# This is a combination of 8 commits.

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

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

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

# The 2nd commit message will be skipped:

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

# The 3rd commit message will be skipped:

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

# The 4th commit message will be skipped:

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

# The 5th commit message will be skipped:

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

# The 6th commit message will be skipped:

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

# The 7th commit message will be skipped:

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

# The 8th commit message will be skipped:

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

# The 9th commit message will be skipped:

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

# The 10th commit message will be skipped:

#	[android] #3891 - simpler list regions iteration

# The 11th commit message will be skipped:

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

# The 12th commit message will be skipped:

#	[android] #3891 - improve description for DEFAULT_MAX_CACHE_SIZE

# The 13th commit message will be skipped:

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

# The 14th commit message will be skipped:

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

# The 15th commit message will be skipped:

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

# The 16th commit message will be skipped:

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

# The 17th commit message will be skipped:

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

# The 18th commit message will be skipped:

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

# The 19th commit message will be skipped:

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

# The 20th commit message will be skipped:

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

# The 21st commit message will be skipped:

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

# The 2nd commit message will be skipped:

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

# The 2nd commit message will be skipped:

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

# The 3rd commit message will be skipped:

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

# The 2nd commit message will be skipped:

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

# The 2nd commit message will be skipped:

#	[android] #3891 - improve description for DEFAULT_MAX_CACHE_SIZE

# The 3rd commit message will be skipped:

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

# The 4th commit message will be skipped:

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

# The 5th commit message will be skipped:

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

# The 6th commit message will be skipped:

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

# The 7th commit message will be skipped:

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

# The 8th commit message will be skipped:

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

View File

@@ -21,7 +21,6 @@
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
@@ -91,22 +90,24 @@
android:label="@string/activity_dynamic_marker" />
<activity
android:name=".MapPaddingActivity"
android:screenOrientation="portrait"
android:label="@string/activity_map_padding" />
android:label="@string/activity_map_padding"
android:screenOrientation="portrait" />
<activity
android:name=".OfflineActivity"
android:label="@string/activity_offline" />
<meta-data
android:name="com.mapbox.AccessToken"
android:value="" />
<meta-data
android:name="com.mapbox.TestEventsServer"
android:value="https://cloudfront-staging.tilestream.net" />
<meta-data
android:name="com.mapbox.TestEventsAccessToken"
android:value="sk.eyJ1IjoiYmxlZWdlIiwiYSI6InNpcml1c2x5In0.KyT-boMyC_xZYTYojTc8zg" />
<service android:name="com.mapbox.mapboxsdk.telemetry.TelemetryService" />
</application>
</manifest>

View File

@@ -472,6 +472,10 @@ public class MainActivity extends AppCompatActivity {
startActivity(new Intent(getApplicationContext(),MapPaddingActivity.class));
return true;
case R.id.action_offline:
startActivity(new Intent(getApplicationContext(), OfflineActivity.class));
return true;
default:
return changeMapStyle(menuItem.getItemId());
}

View File

@@ -0,0 +1,364 @@
package com.mapbox.mapboxsdk.testapp;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
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.OnMapReadyCallback;
import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;
import com.mapbox.mapboxsdk.offline.OfflineRegionError;
import com.mapbox.mapboxsdk.offline.OfflineRegionMetadata;
import com.mapbox.mapboxsdk.offline.OfflineRegionStatus;
import com.mapbox.mapboxsdk.testapp.offline.DownloadRegionDialog;
import com.mapbox.mapboxsdk.testapp.offline.ListRegionsDialog;
import com.mapbox.mapboxsdk.utils.ApiAccess;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
public class OfflineActivity extends AppCompatActivity
implements DownloadRegionDialog.DownloadRegionDialogListener {
private final static String LOG_TAG = "OfflineActivity";
private final static String KEY_REGION_NAME = "KEY_REGION_NAME";
/*
* UI elements
*/
private MapView mMapView;
private MapboxMap mMapboxMap;
private ProgressBar mProgressBar;
private Button downloadRegion;
private Button listRegions;
private boolean isEndNotified;
/*
* Offline objects
*/
private OfflineManager mOfflineManager;
private OfflineRegion mOfflineRegion;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_offline);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
}
// Set up map
mMapView = (MapView) findViewById(R.id.mapView);
mMapView.setAccessToken(ApiAccess.getToken(this));
mMapView.onCreate(savedInstanceState);
mMapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(@NonNull MapboxMap mapboxMap) {
Log.d(LOG_TAG, "Map is ready");
mMapboxMap = mapboxMap;
// Set style
mapboxMap.setStyle(Style.MAPBOX_STREETS);
// Set initial position to UNHQ in NYC
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(
new CameraPosition.Builder()
.target(new LatLng(40.749851, -73.967966))
.zoom(14)
.bearing(0)
.tilt(0)
.build()));
}
});
// The progress bar
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
// Set up button listeners
downloadRegion = (Button) findViewById(R.id.button_download_region);
downloadRegion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleDownloadRegion();
}
});
listRegions = (Button) findViewById(R.id.button_list_regions);
listRegions.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleListRegions();
}
});
// Set up the OfflineManager
mOfflineManager = OfflineManager.getInstance(this);
mOfflineManager.setAccessToken(ApiAccess.getToken(this));
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
public void onPause() {
super.onPause();
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/*
* Buttons logic
*/
private void handleDownloadRegion() {
Log.d(LOG_TAG, "handleDownloadRegion");
// Show dialog
DownloadRegionDialog downloadRegionDialog = new DownloadRegionDialog();
downloadRegionDialog.show(getSupportFragmentManager(), "download");
}
private void handleListRegions() {
Log.d(LOG_TAG, "handleListRegions");
// Query the DB asynchronously
mOfflineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
@Override
public void onList(OfflineRegion[] offlineRegions) {
// Check result
if (offlineRegions == null || offlineRegions.length == 0) {
Log.d(LOG_TAG, "You have no regions yet.");
return;
}
// Get regions info
ArrayList<String> offlineRegionsNames = new ArrayList<>();
for (OfflineRegion offlineRegion : offlineRegions) {
offlineRegionsNames.add(getRegionName(offlineRegion));
}
// Create args
Bundle args = new Bundle();
args.putStringArrayList(ListRegionsDialog.ITEMS, offlineRegionsNames);
// Show dialog
ListRegionsDialog listRegionsDialog = new ListRegionsDialog();
listRegionsDialog.setArguments(args);
listRegionsDialog.show(getSupportFragmentManager(), "list");
}
@Override
public void onError(String error) {
Log.e(LOG_TAG, "Error: " + error);
}
private String getRegionName(OfflineRegion offlineRegion) {
String regionName = "Region " + offlineRegion.getID();
try {
byte[] metadata = offlineRegion.getMetadata().getMetadata();
HashMap<String, String> data = (HashMap<String, String>) OfflineRegionMetadata.deserialize(metadata);
regionName = data.get(KEY_REGION_NAME);
} catch (Exception e) {
Log.e(LOG_TAG, "Failed to decode metadata: " + e.getMessage());
}
return regionName;
}
});
}
/*
* Dialogs
*/
@Override
public void onDownloadRegionDialogPositiveClick(final String regionName) {
Log.d(LOG_TAG, "Download started: " + regionName);
// Start progress bar
startProgress();
// Definition
String styleURL = mMapboxMap.getStyleUrl();
LatLngBounds bounds = mMapboxMap.getProjection().getVisibleRegion().latLngBounds;
double minZoom = 14; // Switch to dynamic once map refactor is complete
double maxZoom = 16; // Switch to dynamic once map refactor is complete
float pixelRatio = this.getResources().getDisplayMetrics().density;
OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
styleURL, bounds, minZoom, maxZoom, pixelRatio);
// Sample way of encoding metadata
OfflineRegionMetadata metadata = null;
try {
HashMap<String, String> data = new HashMap<>();
data.put(KEY_REGION_NAME, regionName);
byte[] dataEncoded = OfflineRegionMetadata.serialize(data);
metadata = new OfflineRegionMetadata(dataEncoded);
} catch (IOException e) {
Log.e(LOG_TAG, "Metadata encoding failed: " + e.getMessage());
}
// Create region
mOfflineManager.createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() {
@Override
public void onCreate(OfflineRegion offlineRegion) {
Log.d(LOG_TAG, "Offline region created: " + regionName);
mOfflineRegion = offlineRegion;
launchDownload();
}
@Override
public void onError(String error) {
Log.e(LOG_TAG, "Error: " + error);
}
});
}
private void launchDownload() {
// Set an observer
mOfflineRegion.setObserver(new OfflineRegion.OfflineRegionObserver() {
@Override
public void onStatusChanged(OfflineRegionStatus status) {
// Compute a percentage
double percentage = status.getRequiredResourceCount() >= 0 ?
(100.0 * status.getCompletedResourceCount() / status.getRequiredResourceCount()) :
0.0;
if (status.isComplete() || percentage >= 98.0 /* Known issue */) {
// Download complete
endProgress("Region downloaded successfully.");
return;
} else if (status.isRequiredResourceCountPrecise()) {
// Switch to determinate state
setPercentage((int) Math.round(percentage));
}
// Debug
Log.d(LOG_TAG, String.format("%s/%s resources; %s bytes downloaded.",
String.valueOf(status.getCompletedResourceCount()),
String.valueOf(status.getRequiredResourceCount()),
String.valueOf(status.getCompletedResourceSize())));
}
@Override
public void onError(OfflineRegionError error) {
Log.e(LOG_TAG, "onError reason: " + error.getReason());
Log.e(LOG_TAG, "onError message: " + error.getMessage());
}
@Override
public void mapboxTileCountLimitExceeded(long limit) {
Log.e(LOG_TAG, "Mapbox tile count limit exceeded: " + limit);
}
});
// Change the region state
mOfflineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);
}
/*
* Progress bar
*/
private void startProgress() {
// Disable buttons
downloadRegion.setEnabled(false);
listRegions.setEnabled(false);
// Start and show the progress bar
isEndNotified = false;
mProgressBar.setIndeterminate(true);
mProgressBar.setVisibility(View.VISIBLE);
}
private void setPercentage(final int percentage) {
mProgressBar.setIndeterminate(false);
mProgressBar.setProgress(percentage);
}
private void endProgress(final String message) {
// Don't notify more than once
if (isEndNotified) return;
// Enable buttons
downloadRegion.setEnabled(true);
listRegions.setEnabled(true);
// Stop and hide the progress bar
isEndNotified = true;
mProgressBar.setIndeterminate(false);
mProgressBar.setVisibility(View.GONE);
// Show a toast
Toast.makeText(OfflineActivity.this, message, Toast.LENGTH_LONG).show();
}
}

View File

@@ -0,0 +1,60 @@
package com.mapbox.mapboxsdk.testapp.offline;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.widget.EditText;
import com.mapbox.mapboxsdk.testapp.R;
/**
* Created by antonio on 2/17/16.
*/
public class DownloadRegionDialog extends DialogFragment {
private final static String LOG_TAG = "DownloadRegionDialog";
public interface DownloadRegionDialogListener {
void onDownloadRegionDialogPositiveClick(String regionName);
}
DownloadRegionDialogListener mListener;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mListener = (DownloadRegionDialogListener) activity;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Let the user choose a name for the region
final EditText regionNameEdit = new EditText(getActivity());
builder.setTitle("Choose a name for the region")
.setIcon(R.drawable.ic_airplanemode_active_black_24dp)
.setView(regionNameEdit)
.setPositiveButton("Start", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String regionName = regionNameEdit.getText().toString();
mListener.onDownloadRegionDialogPositiveClick(regionName);
}
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(LOG_TAG, "Download cancelled.");
}
});
return builder.create();
}
}

View File

@@ -0,0 +1,51 @@
package com.mapbox.mapboxsdk.testapp.offline;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import com.mapbox.mapboxsdk.testapp.R;
import java.util.ArrayList;
/**
* Created by antonio on 2/17/16.
*/
public class ListRegionsDialog extends DialogFragment {
private final static String LOG_TAG = "ListRegionsDialog";
public final static String ITEMS = "ITEMS";
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Read args
Bundle args = getArguments();
ArrayList<String> offlineRegionsNames = (args == null ? null : args.getStringArrayList(ITEMS));
CharSequence[] items = offlineRegionsNames.toArray(new CharSequence[offlineRegionsNames.size()]);
builder.setTitle("List of offline regions")
.setIcon(R.drawable.ic_airplanemode_active_black_24dp)
.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(LOG_TAG, "Selected item: " + which);
}
})
.setPositiveButton("Accept", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(LOG_TAG, "Dialog dismissed");
}
});
return builder.create();
}
}

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10.18,9"/>
<path
android:fillColor="#FF000000"
android:pathData="M21,16v-2l-8,-5V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5V9l-8,5v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-5.5l8,2.5z"/>
</vector>

View File

@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"

View File

@@ -0,0 +1,51 @@
<?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:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<ProgressBar
android:id="@+id/progress_bar"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="25dp"
android:layout_below="@+id/toolbar"
style="@android:style/Widget.Material.ProgressBar.Horizontal" />
<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/progress_bar" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/button_download_region"
android:id="@+id/button_download_region"
android:layout_margin="@dimen/fab_margin"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:background="@color/white"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/button_list_regions"
android:id="@+id/button_list_regions"
android:layout_alignBottom="@+id/button_download_region"
android:layout_alignParentRight="true"
android:layout_marginRight="@dimen/fab_margin"
android:background="@color/white"/>
</RelativeLayout>

View File

@@ -72,6 +72,12 @@
android:title="@string/navdrawer_menu_title_individual_tests">
<menu>
<item
android:id="@+id/action_offline"
android:checkable="false"
android:icon="@drawable/ic_airplanemode_active_black_24dp"
android:title="@string/action_offline" />
<item
android:id="@+id/action_mapboxmap"
android:checkable="false"

View File

@@ -6,5 +6,4 @@
<dimen name="map_padding_left">96dp</dimen>
<dimen name="map_padding_bottom">256dp</dimen>
<dimen name="map_padding_right">32dp</dimen>
</resources>

View File

@@ -27,6 +27,7 @@
<string name="activity_scroll_by">Scroll By Activity</string>
<string name="activity_dynamic_marker">Dynamic Marker Activity</string>
<string name="activity_map_padding">Map Padding Activity</string>
<string name="activity_offline">Offline Map Activity</string>
<string name="navdrawer_menu_title_mainactivity_controls">Main Activity Controls</string>
<string name="navdrawer_menu_title_mainactivity_styles">Main Activity Styles</string>
@@ -56,6 +57,7 @@
<string name="action_double_mapview">Double MapView</string>
<string name="action_dynamic_marker">Dynamic Marker</string>
<string name="action_map_padding">Map Padding</string>
<string name="action_offline">Offline Map</string>
<string name="button_camera_move">Move</string>
<string name="button_camera_ease">Ease</string>
@@ -98,5 +100,7 @@
<string name="scrollby_x_value">X: %1$d</string>
<string name="scrollby_y_value">Y: %1$d</string>
<string name="button_download_region">Download region</string>
<string name="button_list_regions">List regions</string>
</resources>