From 4e01377087931fc69e93858db64dbf8617ea8ae1 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Tue, 4 Apr 2017 15:39:18 +0200 Subject: [PATCH] [android] - derived source attribution --- .../maps/AttributionDialogManager.java | 169 ++++++++++++++++++ .../com/mapbox/mapboxsdk/maps/MapView.java | 81 +-------- .../mapboxsdk/style/sources/Source.java | 14 ++ .../src/main/res-public/values/public.xml | 3 +- .../src/main/res/values/arrays.xml | 16 -- .../src/main/res/values/strings.xml | 2 + .../testapp/maps/widgets/AttributionTest.java | 103 ++++++++--- platform/android/src/style/sources/source.cpp | 8 +- platform/android/src/style/sources/source.hpp | 2 + 9 files changed, 278 insertions(+), 120 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java new file mode 100644 index 00000000000..2ae37acb9c5 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java @@ -0,0 +1,169 @@ +package com.mapbox.mapboxsdk.maps; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.Html; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.URLSpan; +import android.view.View; +import android.widget.ArrayAdapter; + +import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.style.sources.Source; +import com.mapbox.services.android.telemetry.MapboxTelemetry; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Locale; + +/** + * Responsible for managing attribution interactions on the map. + *

+ * When the user clicks the attribution icon, {@link AttributionDialogManager#onClick(View)} will be invoked. + * An attribution dialog will be shown to the user with contents based on the attributions found in the map style. + * Additionally an telemetry option item is shown to configure telemetry settings. + *

+ */ +class AttributionDialogManager implements View.OnClickListener, DialogInterface.OnClickListener { + + private static final String MAP_FEEDBACK_URL = "https://www.mapbox.com/map-feedback"; + private static final String MAP_FEEDBACK_LOCATION_FORMAT = MAP_FEEDBACK_URL + "/#/%f/%f/%d"; + + private final Context context; + private final MapboxMap mapboxMap; + private String[] attributionKeys; + private HashMap attributionMap; + + AttributionDialogManager(@NonNull Context context, @NonNull MapboxMap mapboxMap) { + this.context = context; + this.mapboxMap = mapboxMap; + } + + // Called when someone presses the attribution icon on the map + @Override + public void onClick(View view) { + attributionMap = new AttributionBuilder(context, mapboxMap).build(); + showAttributionDialog(); + } + + private void showAttributionDialog() { + attributionKeys = attributionMap.keySet().toArray(new String[attributionMap.size()]); + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + builder.setTitle(R.string.mapbox_attributionsDialogTitle); + builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionKeys), this); + builder.show(); + } + + // Called when someone selects an attribution or telemetry settings from the dialog + @Override + public void onClick(DialogInterface dialog, int which) { + if (isLatestEntry(which)) { + showTelemetryDialog(); + } else { + showAttributionWebPage(which); + } + } + + private boolean isLatestEntry(int attributionKeyIndex) { + return attributionKeyIndex == attributionKeys.length - 1; + } + + private void showTelemetryDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + builder.setTitle(R.string.mapbox_attributionTelemetryTitle); + builder.setMessage(R.string.mapbox_attributionTelemetryMessage); + builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + MapboxTelemetry.getInstance().setTelemetryEnabled(true); + dialog.cancel(); + } + }); + builder.setNeutralButton(R.string.mapbox_attributionTelemetryNeutral, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String url = context.getResources().getString(R.string.mapbox_telemetryLink); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + context.startActivity(intent); + dialog.cancel(); + } + }); + builder.setNegativeButton(R.string.mapbox_attributionTelemetryNegative, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + MapboxTelemetry.getInstance().setTelemetryEnabled(false); + dialog.cancel(); + } + }); + builder.show(); + } + + private void showAttributionWebPage(int which) { + Intent intent = new Intent(Intent.ACTION_VIEW); + String url = attributionMap.get(attributionKeys[which]); + if (url.contains(MAP_FEEDBACK_URL)) { + url = buildMapFeedbackMapUrl(mapboxMap.getCameraPosition()); + } + intent.setData(Uri.parse(url)); + context.startActivity(intent); + } + + private String buildMapFeedbackMapUrl(CameraPosition cameraPosition) { + // appends current location to the map feedback url if available + return cameraPosition != null ? String.format(Locale.getDefault(), + MAP_FEEDBACK_LOCATION_FORMAT, cameraPosition.target.getLongitude(), cameraPosition.target.getLatitude(), + (int) cameraPosition.zoom) : MAP_FEEDBACK_URL; + } + + private static class AttributionBuilder { + + private final HashMap map = new LinkedHashMap<>(); + private final Context context; + private final MapboxMap mapboxMap; + + AttributionBuilder(Context context, MapboxMap mapboxMap) { + this.context = context.getApplicationContext(); + this.mapboxMap = mapboxMap; + } + + private HashMap build() { + for (Source source : mapboxMap.getSources()) { + parseAttribution(source.getAttribution()); + } + addTelemetryEntryToAttributionMap(); + return map; + } + + private void parseAttribution(String attributionSource) { + if (!TextUtils.isEmpty(attributionSource)) { + SpannableStringBuilder htmlBuilder = (SpannableStringBuilder) Html.fromHtml(attributionSource); + URLSpan[] urlSpans = htmlBuilder.getSpans(0, htmlBuilder.length(), URLSpan.class); + for (URLSpan urlSpan : urlSpans) { + map.put(resolveAnchorValue(htmlBuilder, urlSpan), urlSpan.getURL()); + } + } + } + + private String resolveAnchorValue(SpannableStringBuilder htmlBuilder, URLSpan urlSpan) { + int start = htmlBuilder.getSpanStart(urlSpan); + int end = htmlBuilder.getSpanEnd(urlSpan); + int length = end - start; + char[] charKey = new char[length]; + htmlBuilder.getChars(start, end, charKey, 0); + return String.valueOf(charKey); + } + + private void addTelemetryEntryToAttributionMap() { + String telemetryKey = context.getString(R.string.mapbox_telemetrySettings); + String telemetryLink = context.getString(R.string.mapbox_telemetryLink); + map.put(telemetryKey, telemetryLink); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 8c8b70d788c..46db93fa28d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -1,15 +1,11 @@ package com.mapbox.mapboxsdk.maps; import android.app.Activity; -import android.app.Dialog; import android.app.Fragment; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.SurfaceTexture; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.support.annotation.CallSuper; @@ -17,7 +13,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import android.support.v7.app.AlertDialog; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -28,7 +23,6 @@ import android.view.TextureView; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; @@ -36,7 +30,6 @@ import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; -import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.maps.widgets.CompassView; @@ -155,7 +148,7 @@ private void initialise(@NonNull final Context context, @NonNull final MapboxMap // inject widgets with MapboxMap compassView.setMapboxMap(mapboxMap); myLocationView.setMapboxMap(mapboxMap); - attrView.setOnClickListener(new AttributionOnClickListener(context, transform)); + attrView.setOnClickListener(new AttributionDialogManager(context, mapboxMap)); // Ensure this view is interactable setClickable(true); @@ -593,78 +586,6 @@ void setMapboxMap(MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; } - private static class AttributionOnClickListener implements View.OnClickListener, DialogInterface.OnClickListener { - - private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2; - private static final int ATTRIBUTION_INDEX_TELEMETRY_SETTINGS = 3; - private Context context; - private Transform transform; - - public AttributionOnClickListener(Context context, Transform transform) { - this.context = context; - this.transform = transform; - } - - // Called when someone presses the attribution icon - @Override - public void onClick(View view) { - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); - builder.setTitle(R.string.mapbox_attributionsDialogTitle); - String[] items = context.getResources().getStringArray(R.array.mapbox_attribution_names); - builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, items), this); - builder.show(); - } - - // Called when someone selects an attribution, 'Improve this map' adds location data to the url - @Override - public void onClick(DialogInterface dialog, int which) { - final Context context = ((Dialog) dialog).getContext(); - if (which == ATTRIBUTION_INDEX_TELEMETRY_SETTINGS) { - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); - builder.setTitle(R.string.mapbox_attributionTelemetryTitle); - builder.setMessage(R.string.mapbox_attributionTelemetryMessage); - builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - MapboxTelemetry.getInstance().setTelemetryEnabled(true); - dialog.cancel(); - } - }); - builder.setNeutralButton(R.string.mapbox_attributionTelemetryNeutral, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String url = context.getResources().getStringArray(R.array.mapbox_attribution_links)[3]; - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - context.startActivity(intent); - dialog.cancel(); - } - }); - builder.setNegativeButton(R.string.mapbox_attributionTelemetryNegative, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - MapboxTelemetry.getInstance().setTelemetryEnabled(false); - dialog.cancel(); - } - }); - - builder.show(); - return; - } - String url = context.getResources().getStringArray(R.array.mapbox_attribution_links)[which]; - if (which == ATTRIBUTION_INDEX_IMPROVE_THIS_MAP) { - CameraPosition cameraPosition = transform.getCameraPosition(); - if (cameraPosition != null) { - url = String.format(url, cameraPosition.target.getLongitude(), - cameraPosition.target.getLatitude(), (int) cameraPosition.zoom); - } - } - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - context.startActivity(intent); - } - } - /** * Definition of a map change event. * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/Source.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/Source.java index 62bfcb818f0..22b22445371 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/Source.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/Source.java @@ -27,6 +27,18 @@ public String getId() { return nativeGetId(); } + /** + * Retrieve the source attribution. + *

+ * Will return an empty String if no attribution is available. + *

+ * + * @return the string representation of the attribution in html format + */ + public String getAttribution() { + return nativeGetAttribution(); + } + /** * Internal use * @@ -38,4 +50,6 @@ public long getNativePtr() { protected native String nativeGetId(); + protected native String nativeGetAttribution(); + } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml index a64fad39c21..fe94d82baa9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml @@ -89,7 +89,7 @@ - + @@ -100,6 +100,7 @@ + diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml deleted file mode 100644 index 5a5fd6cb4ce..00000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - © Mapbox - © OpenStreetMap - Improve this map - Telemetry Settings - - - - https://www.mapbox.com/about/maps/ - http://www.openstreetmap.org/about/ - https://www.mapbox.com/map-feedback/#/%1$f/%2$f/%3$d - https://www.mapbox.com/telemetry/ - - diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml index cf7707d37e9..6c427d540b4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml @@ -11,6 +11,8 @@ Disagree More info Provided OfflineRegionDefinition doesn\'t fit the world bounds: %s + Telemetry Settings + https://www.mapbox.com/telemetry/