From f0f113bafc49a735a596357a0982e298648f4d48 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 14 Nov 2017 11:13:34 +0100 Subject: MapSnapshot attribution (#10362) * [android] - add attribution * [android] - optimise attribution sources * [android] - rework datamodel to attribution class * [android] - refactor Attribution, add tests * [android] - add getter for attribution string * [android] - rework attribution to include small logo, add layout placement * [android] - finalise integration and layout logic --- platform/android/MapboxGLAndroidSDK/build.gradle | 5 +- .../mapbox/mapboxsdk/attribution/Attribution.java | 59 ++++ .../mapboxsdk/attribution/AttributionLayout.java | 62 +++++ .../mapboxsdk/attribution/AttributionMeasure.java | 230 +++++++++++++++ .../mapboxsdk/attribution/AttributionParser.java | 257 +++++++++++++++++ .../com/mapbox/mapboxsdk/http/HTTPRequest.java | 1 + .../mapboxsdk/maps/AttributionDialogManager.java | 77 +++-- .../mapbox/mapboxsdk/snapshotter/MapSnapshot.java | 2 +- .../mapboxsdk/snapshotter/MapSnapshotter.java | 179 ++++++++++-- .../main/res/drawable-hdpi/mapbox_logo_helmet.png | Bin 0 -> 1650 bytes .../main/res/drawable-mdpi/mapbox_logo_helmet.png | Bin 0 -> 950 bytes .../main/res/drawable-xhdpi/mapbox_logo_helmet.png | Bin 0 -> 2184 bytes .../res/drawable-xxhdpi/mapbox_logo_helmet.png | Bin 0 -> 3389 bytes .../res/drawable-xxxhdpi/mapbox_logo_helmet.png | Bin 0 -> 4468 bytes .../main/res/drawable/mapbox_rounded_corner.xml | 10 + .../src/main/res/values/colors.xml | 1 + .../attribution/AttributionParseTest.java | 309 +++++++++++++++++++++ .../activity/snapshot/MapSnapshotterActivity.java | 2 +- .../snapshot/MapSnapshotterMarkerActivity.java | 10 +- platform/android/dependencies.gradle | 1 + 20 files changed, 1128 insertions(+), 77 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/mapbox_logo_helmet.png create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/mapbox_logo_helmet.png create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/mapbox_logo_helmet.png create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/mapbox_logo_helmet.png create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/mapbox_logo_helmet.png create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_rounded_corner.xml create mode 100644 platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle index bf0af3052b..ce886b1001 100644 --- a/platform/android/MapboxGLAndroidSDK/build.gradle +++ b/platform/android/MapboxGLAndroidSDK/build.gradle @@ -10,6 +10,7 @@ dependencies { } testCompile rootProject.ext.dep.junit testCompile rootProject.ext.dep.mockito + testCompile rootProject.ext.dep.robolectric // Mapbox Android Services (GeoJSON support) compile(rootProject.ext.dep.mapboxJavaGeoJSON) { @@ -126,7 +127,9 @@ android { } testOptions { - unitTests.returnDefaultValues = true + unitTests{ + returnDefaultValues = true + } } buildTypes { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java new file mode 100644 index 0000000000..0877b3ab97 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java @@ -0,0 +1,59 @@ +package com.mapbox.mapboxsdk.attribution; + +public class Attribution { + + private static final String OPENSTREETMAP = "OpenStreetMap"; + private static final String OPENSTREETMAP_ABBR = "OSM"; + static final String TELEMETRY = "Telemetry Settings"; + + static final String IMPROVE_MAP_URL = "https://www.mapbox.com/map-feedback/"; + static final String MAPBOX_URL = "https://www.mapbox.com/about/maps/"; + static final String TELEMETRY_URL = "https://www.mapbox.com/telemetry/"; + + private String title; + private String url; + + Attribution(String title, String url) { + this.title = title; + this.url = url; + } + + public String getTitle() { + return title; + } + + public String getTitleAbbreviated() { + if (title.equals(OPENSTREETMAP)) { + return OPENSTREETMAP_ABBR; + } + return title; + } + + public String getUrl() { + return url; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Attribution that = (Attribution) o; + + if (title != null ? !title.equals(that.title) : that.title != null) { + return false; + } + return url != null ? url.equals(that.url) : that.url == null; + } + + @Override + public int hashCode() { + int result = title != null ? title.hashCode() : 0; + result = 31 * result + (url != null ? url.hashCode() : 0); + return result; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java new file mode 100644 index 0000000000..b08a8353be --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java @@ -0,0 +1,62 @@ +package com.mapbox.mapboxsdk.attribution; + +import android.graphics.Bitmap; +import android.graphics.PointF; +import android.support.annotation.Nullable; + +public class AttributionLayout { + + private Bitmap logo; + private PointF anchorPoint; + private boolean shortText; + + public AttributionLayout(@Nullable Bitmap logo, @Nullable PointF anchorPoint, boolean shortText) { + this.logo = logo; + this.anchorPoint = anchorPoint; + this.shortText = shortText; + } + + public Bitmap getLogo() { + return logo; + } + + public PointF getAnchorPoint() { + return anchorPoint; + } + + public boolean isShortText() { + return shortText; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AttributionLayout that = (AttributionLayout) o; + + if (logo != null ? !logo.equals(that.logo) : that.logo != null) { + return false; + } + return anchorPoint != null ? anchorPoint.equals(that.anchorPoint) : that.anchorPoint == null; + } + + @Override + public int hashCode() { + int result = logo != null ? logo.hashCode() : 0; + result = 31 * result + (anchorPoint != null ? anchorPoint.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "AttributionLayout{" + + "logo=" + logo + + ", anchorPoint=" + anchorPoint + + '}'; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java new file mode 100644 index 0000000000..667060168b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java @@ -0,0 +1,230 @@ +package com.mapbox.mapboxsdk.attribution; + +import android.graphics.Bitmap; +import android.graphics.PointF; +import android.widget.TextView; + +import java.util.Arrays; +import java.util.List; + +public class AttributionMeasure { + + private Bitmap logo; + private Bitmap logoSmall; + private Bitmap snapshot; + private TextView textView; + private TextView textViewShort; + private float margin; + + private boolean shorterText; + + AttributionMeasure(Bitmap snapshot, Bitmap logo, Bitmap logoSmall, TextView tv, TextView tvShort, float margin) { + this.snapshot = snapshot; + this.logo = logo; + this.logoSmall = logoSmall; + this.textView = tv; + this.textViewShort = tvShort; + this.margin = margin; + } + + public AttributionLayout measure() { + Chain chain = new Chain( + new FullLogoLongTextCommand(), + new FullLogoShortTextCommand(), + new SmallLogoLongTextCommand(), + new SmallLogoShortTextCommand(), + new LongTextCommand(), + new ShortTextCommand(), + new NoTextCommand() + ); + + AttributionLayout attributionLayout = chain.start(this); + shorterText = attributionLayout.isShortText(); + return attributionLayout; + } + + + private static class FullLogoLongTextCommand implements Command { + public AttributionLayout execute(AttributionMeasure measure) { + float width = measure.getLogoContainerWidth() + measure.getTextViewContainerWidth(); + boolean fitBounds = width <= measure.getMaxSize(); + if (fitBounds) { + PointF anchor = calculateAnchor(measure.snapshot, measure.textView, measure.margin); + return new AttributionLayout(measure.logo, anchor, false); + } + return null; + } + } + + private static class FullLogoShortTextCommand implements Command { + @Override + public AttributionLayout execute(AttributionMeasure measure) { + float width = measure.getLogoContainerWidth() + measure.getTextViewShortContainerWidth(); + boolean fitBounds = width <= measure.getMaxSizeShort(); + if (fitBounds) { + PointF anchor = calculateAnchor(measure.snapshot, measure.textView, measure.margin); + return new AttributionLayout(measure.logo, anchor, true); + } + return null; + } + } + + private static class SmallLogoLongTextCommand implements Command { + @Override + public AttributionLayout execute(AttributionMeasure measure) { + float width = measure.getLogoSmallContainerWidth() + measure.getTextViewContainerWidth(); + boolean fitBounds = width <= measure.getMaxSize(); + if (fitBounds) { + PointF anchor = calculateAnchor(measure.snapshot, measure.textView, measure.margin); + return new AttributionLayout(measure.logoSmall, anchor, false); + } + return null; + } + } + + private static class SmallLogoShortTextCommand implements Command { + @Override + public AttributionLayout execute(AttributionMeasure measure) { + float width = measure.getLogoContainerWidth() + measure.getTextViewShortContainerWidth(); + boolean fitBounds = width <= measure.getMaxSizeShort(); + if (fitBounds) { + PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin); + return new AttributionLayout(measure.logoSmall, anchor, true); + } + return null; + } + } + + private static class LongTextCommand implements Command { + @Override + public AttributionLayout execute(AttributionMeasure measure) { + float width = measure.getTextViewContainerWidth() + measure.margin; + boolean fitBounds = width <= measure.getMaxSize(); + if (fitBounds) { + return new AttributionLayout(null, calculateAnchor(measure.snapshot, measure.textView, measure.margin), false); + } + return null; + } + } + + private static class ShortTextCommand implements Command { + @Override + public AttributionLayout execute(AttributionMeasure measure) { + float width = measure.getTextViewShortContainerWidth() + measure.margin; + boolean fitBounds = width <= measure.getMaxSizeShort(); + if (fitBounds) { + PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin); + return new AttributionLayout(null, anchor, true); + } + return null; + } + } + + private static class NoTextCommand implements Command { + @Override + public AttributionLayout execute(AttributionMeasure measure) { + return new AttributionLayout(null, null, false); + } + } + + private static PointF calculateAnchor(Bitmap snapshot, TextView textView, float margin) { + return new PointF( + snapshot.getWidth() - textView.getMeasuredWidth() - margin, + snapshot.getHeight() - margin - textView.getMeasuredHeight() + ); + } + + public TextView getTextView() { + return shorterText ? textViewShort : textView; + } + + private class Chain { + public List commands; + + Chain(Command... commands) { + this.commands = Arrays.asList(commands); + } + + public AttributionLayout start(AttributionMeasure measure) { + AttributionLayout attributionLayout = null; + for (Command command : commands) { + attributionLayout = command.execute(measure); + if (attributionLayout != null) { + break; + } + } + return attributionLayout; + } + } + + public interface Command { + AttributionLayout execute(AttributionMeasure measure); + } + + private float getTextViewContainerWidth() { + return textView.getMeasuredWidth() + margin; + } + + private float getLogoContainerWidth() { + return logo.getWidth() + (2 * margin); + } + + private float getTextViewShortContainerWidth() { + return textViewShort.getMeasuredWidth() + margin; + } + + private float getLogoSmallContainerWidth() { + return logoSmall.getWidth() + (2 * margin); + } + + private float getMaxSize() { + return snapshot.getWidth() * 8 / 10; + } + + private float getMaxSizeShort() { + return snapshot.getWidth(); + } + + public static class Builder { + private Bitmap snapshot; + private Bitmap logo; + private Bitmap logoSmall; + private TextView textView; + private TextView textViewShort; + private float marginPadding; + + public Builder setSnapshot(Bitmap snapshot) { + this.snapshot = snapshot; + return this; + } + + public Builder setLogo(Bitmap logo) { + this.logo = logo; + return this; + } + + public Builder setLogoSmall(Bitmap logoSmall) { + this.logoSmall = logoSmall; + return this; + } + + public Builder setTextView(TextView textView) { + this.textView = textView; + return this; + } + + public Builder setTextViewShort(TextView textViewShort) { + this.textViewShort = textViewShort; + return this; + } + + public Builder setMarginPadding(float marginPadding) { + this.marginPadding = marginPadding; + return this; + } + + public AttributionMeasure build() { + return new AttributionMeasure(snapshot, logo, logoSmall, textView, textViewShort, marginPadding); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java new file mode 100644 index 0000000000..90bb23429f --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java @@ -0,0 +1,257 @@ +package com.mapbox.mapboxsdk.attribution; + +import android.text.Html; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.URLSpan; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Responsible for parsing attribution data coming from Sources and MapSnapshot. + *

+ * Exposes multiple configuration options to manipulate data being parsed. + * Use the Options object to build these configurations. + *

+ */ +public class AttributionParser { + + private final Set attributions = new LinkedHashSet<>(); + private final String attributionData; + private final boolean withImproveMap; + private final boolean withCopyrightSign; + private final boolean withTelemetryAttribution; + private final boolean withMapboxAttribution; + + AttributionParser(String attributionData, boolean withImproveMap, boolean withCopyrightSign, + boolean withTelemetryAttribution, boolean withMapboxAttribution) { + this.attributionData = attributionData; + this.withImproveMap = withImproveMap; + this.withCopyrightSign = withCopyrightSign; + this.withTelemetryAttribution = withTelemetryAttribution; + this.withMapboxAttribution = withMapboxAttribution; + } + + /** + * Get parsed attributions. + * + * @return the attributions + */ + public Set getAttributions() { + return attributions; + } + + /** + * Get parsed attribution string. + * + * @return the parsed attribution string + */ + public String createAttributionString() { + return createAttributionString(false); + } + + /** + * Get parsed attribution string. + * + * @param shortenedOutput if attribution string should contain shortened output + * @return the parsed attribution string + */ + public String createAttributionString(boolean shortenedOutput) { + StringBuilder stringBuilder = new StringBuilder(withCopyrightSign ? "" : "© "); + int counter = 0; + for (Attribution attribution : attributions) { + counter++; + stringBuilder.append(!shortenedOutput ? attribution.getTitle() : attribution.getTitleAbbreviated()); + if (counter != attributions.size()) { + stringBuilder.append(" / "); + } + } + return stringBuilder.toString(); + } + + /** + * Main attribution for configuration + */ + protected void parse() { + parseAttributions(); + addAdditionalAttributions(); + } + + /** + * Parse attributions + */ + private void parseAttributions() { + SpannableStringBuilder htmlBuilder = (SpannableStringBuilder) fromHtml(attributionData); + URLSpan[] urlSpans = htmlBuilder.getSpans(0, htmlBuilder.length(), URLSpan.class); + for (URLSpan urlSpan : urlSpans) { + parseUrlSpan(htmlBuilder, urlSpan); + } + } + + /** + * Parse an URLSpan containing an attribution. + * + * @param htmlBuilder the html builder + * @param urlSpan the url span to be parsed + */ + private void parseUrlSpan(SpannableStringBuilder htmlBuilder, URLSpan urlSpan) { + String url = urlSpan.getURL(); + if (isUrlValid(url)) { + String anchor = parseAnchorValue(htmlBuilder, urlSpan); + attributions.add(new Attribution(anchor, url)); + } + } + + /** + * Invoked to validate if an url is valid to be included in the final attribution. + * + * @param url the url to be validated + * @return if the url is valid + */ + private boolean isUrlValid(String url) { + return isValidForImproveThisMap(url) && isValidForMapbox(url); + } + + /** + * Invoked to validate if an url is valid for the improve map configuration. + * + * @param url the url to be validated + * @return if the url is valid for improve this map + */ + private boolean isValidForImproveThisMap(String url) { + return withImproveMap || !url.equals(Attribution.IMPROVE_MAP_URL); + } + + /** + * Invoked to validate if an url is valid for the Mapbox configuration. + * + * @param url the url to be validated + * @return if the url is valid for Mapbox + */ + private boolean isValidForMapbox(String url) { + return withMapboxAttribution || !url.equals(Attribution.MAPBOX_URL); + } + + /** + * Parse the attribution by parsing the anchor value of html href tag. + * + * @param htmlBuilder the html builder + * @param urlSpan the current urlSpan + * @return the parsed anchor value + */ + private String parseAnchorValue(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 stripCopyright(String.valueOf(charKey)); + } + + /** + * Utility to strip the copyright sign from an attribution + * + * @param anchor the attribution string to strip + * @return the stripped attribution string without the copyright sign + */ + private String stripCopyright(String anchor) { + if (!withCopyrightSign && anchor.startsWith("© ")) { + anchor = anchor.substring(2, anchor.length()); + } + return anchor; + } + + /** + * Invoked to manually add attributions + */ + private void addAdditionalAttributions() { + if (withTelemetryAttribution) { + attributions.add(new Attribution(Attribution.TELEMETRY, Attribution.TELEMETRY_URL)); + } + } + + /** + * Convert a string to a spanned html representation. + * + * @param html the string to convert + * @return the spanned html representation + */ + private static Spanned fromHtml(String html) { + Spanned result; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); + } else { + result = Html.fromHtml(html); + } + return result; + } + + /** + * Builder to configure using an AttributionParser. + *

+ * AttributionData, set with {@link #withAttributionData(String...)}, is the only required property to build + * the underlying AttributionParser. Other properties include trimming the copyright sign, adding telemetry + * attribution or hiding attribution as improve this map and Mapbox. + *

+ */ + public static class Options { + private boolean withImproveMap = true; + private boolean withCopyrightSign = true; + private boolean withTelemetryAttribution = false; + private boolean withMapboxAttribution = true; + private String[] attributionDataStringArray; + + public Options withAttributionData(String... attributionData) { + this.attributionDataStringArray = attributionData; + return this; + } + + public Options withImproveMap(boolean withImproveMap) { + this.withImproveMap = withImproveMap; + return this; + } + + public Options withCopyrightSign(boolean withCopyrightSign) { + this.withCopyrightSign = withCopyrightSign; + return this; + } + + public Options withTelemetryAttribution(boolean withTelemetryAttribution) { + this.withTelemetryAttribution = withTelemetryAttribution; + return this; + } + + public Options withMapboxAttribution(boolean withMapboxAttribution) { + this.withMapboxAttribution = withMapboxAttribution; + return this; + } + + public AttributionParser build() { + if (attributionDataStringArray == null) { + throw new IllegalStateException("Using builder without providing attribution data"); + } + + String fullAttributionString = parseAttribution(attributionDataStringArray); + AttributionParser attributionParser = new AttributionParser( + fullAttributionString, + withImproveMap, + withCopyrightSign, + withTelemetryAttribution, + withMapboxAttribution + ); + attributionParser.parse(); + return attributionParser; + } + + private String parseAttribution(String[] attribution) { + StringBuilder builder = new StringBuilder(); + for (String attr : attribution) { + if (!attr.isEmpty()) { + builder.append(attr); + } + } + return builder.toString(); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java index 32aa250997..9f887ab7cc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java @@ -55,6 +55,7 @@ class HTTPRequest implements Callback { private HTTPRequest(long nativePtr, String resourceUrl, String etag, String modified) { mNativePtr = nativePtr; + Timber.e("requesting: %s",resourceUrl); try { HttpUrl httpUrl = HttpUrl.parse(resourceUrl); final String host = httpUrl.host().toLowerCase(MapboxConstants.MAPBOX_LOCALE); 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 index 9ccff387f5..2956d864e6 100644 --- 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 @@ -7,22 +7,20 @@ import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; -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 android.widget.Toast; - import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.attribution.Attribution; +import com.mapbox.mapboxsdk.attribution.AttributionParser; 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.ArrayList; +import java.util.List; import java.util.Locale; +import java.util.Set; /** * Responsible for managing attribution interactions on the map. @@ -39,8 +37,8 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. private final Context context; private final MapboxMap mapboxMap; - private String[] attributionKeys; - private HashMap attributionMap; + private String[] attributionTitles; + private Set attributionSet; AttributionDialogManager(@NonNull Context context, @NonNull MapboxMap mapboxMap) { this.context = context; @@ -50,18 +48,26 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. // Called when someone presses the attribution icon on the map @Override public void onClick(View view) { - attributionMap = new AttributionBuilder(context, mapboxMap).build(); + attributionSet = new AttributionBuilder(mapboxMap).build(); showAttributionDialog(); } private void showAttributionDialog() { - attributionKeys = attributionMap.keySet().toArray(new String[attributionMap.size()]); + attributionTitles = getAttributionTitles(); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.mapbox_attributionsDialogTitle); - builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionKeys), this); + builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionTitles), this); builder.show(); } + private String[] getAttributionTitles() { + List titles = new ArrayList<>(); + for (Attribution attribution : attributionSet) { + titles.add(attribution.getTitle()); + } + return titles.toArray(new String[titles.size()]); + } + // Called when someone selects an attribution or telemetry settings from the dialog @Override public void onClick(DialogInterface dialog, int which) { @@ -73,7 +79,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. } private boolean isLatestEntry(int attributionKeyIndex) { - return attributionKeyIndex == attributionKeys.length - 1; + return attributionKeyIndex == attributionTitles.length - 1; } private void showTelemetryDialog() { @@ -105,7 +111,8 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. } private void showMapFeedbackWebPage(int which) { - String url = attributionMap.get(attributionKeys[which]); + Attribution[] attributions = attributionSet.toArray(new Attribution[attributionSet.size()]); + String url = attributions[which].getUrl(); if (url.contains(MAP_FEEDBACK_URL)) { url = buildMapFeedbackMapUrl(mapboxMap.getCameraPosition()); } @@ -132,46 +139,24 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. 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(); + AttributionBuilder(MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; } - private HashMap build() { + private Set build() { + List attributions = new ArrayList<>(); 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()); - } + attributions.add(source.getAttribution()); } - } - - 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); + return new AttributionParser.Options() + .withCopyrightSign(true) + .withImproveMap(true) + .withTelemetryAttribution(true) + .withAttributionData(attributions.toArray(new String[attributions.size()])) + .build().getAttributions(); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java index eb4f94c428..38c1491461 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java @@ -28,7 +28,7 @@ public class MapSnapshot { } /** - * @return the bitmap + * @return the large */ public Bitmap getBitmap() { return bitmap; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java index 5deedc3e63..1c59bb468e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -5,19 +5,31 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.PointF; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.v4.content.res.ResourcesCompat; +import android.text.Html; import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.attribution.AttributionLayout; +import com.mapbox.mapboxsdk.attribution.AttributionMeasure; +import com.mapbox.mapboxsdk.attribution.AttributionParser; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.storage.FileSource; +import timber.log.Timber; + /** - * The map snapshotter creates a bitmap of the map, rendered + * The map snapshotter creates a large of the map, rendered * off the UI thread. The snapshotter itself must be used on * the UI thread (for access to the main looper) */ @@ -269,43 +281,126 @@ public class MapSnapshotter { * @param mapSnapshot the map snapshot to draw the overlay on */ protected void addOverlay(MapSnapshot mapSnapshot) { - Bitmap original = mapSnapshot.getBitmap(); - Canvas canvas = new Canvas(original); - addLogo(canvas, original); + Bitmap snapshot = mapSnapshot.getBitmap(); + Canvas canvas = new Canvas(snapshot); + int margin = (int) context.getResources().getDisplayMetrics().density * LOGO_MARGIN_DP; + drawOverlay(mapSnapshot, snapshot, canvas, margin); + } + + private void drawOverlay(MapSnapshot mapSnapshot, Bitmap snapshot, Canvas canvas, int margin) { + AttributionMeasure measure = getAttributionMeasure(mapSnapshot, snapshot, margin); + AttributionLayout layout = measure.measure(); + drawLogo(mapSnapshot, canvas, margin, layout); + drawAttribution(mapSnapshot, canvas, measure, layout); + } + + private AttributionMeasure getAttributionMeasure(MapSnapshot mapSnapshot, Bitmap snapshot, int margin) { + Logo logo = createScaledLogo(snapshot); + TextView longText = createTextView(mapSnapshot, false, logo.getScale()); + TextView shortText = createTextView(mapSnapshot, true, logo.getScale()); + + return new AttributionMeasure.Builder() + .setSnapshot(snapshot) + .setLogo(logo.getLarge()) + .setLogoSmall(logo.getSmall()) + .setTextView(longText) + .setTextViewShort(shortText) + .setMarginPadding(margin) + .build(); + } + + private void drawLogo(MapSnapshot mapSnapshot, Canvas canvas, int margin, AttributionLayout layout) { + if (mapSnapshot.isShowLogo()) { + drawLogo(mapSnapshot.getBitmap(), canvas, margin, layout); + } + } + + private void drawLogo(Bitmap snapshot, Canvas canvas, int margin, AttributionLayout placement) { + Bitmap selectedLogo = placement.getLogo(); + if (selectedLogo != null) { + canvas.drawBitmap(selectedLogo, margin, snapshot.getHeight() - selectedLogo.getHeight() - margin, null); + } + } + + private void drawAttribution(MapSnapshot mapSnapshot, Canvas canvas, + AttributionMeasure measure, AttributionLayout layout) { + // draw attribution + PointF anchorPoint = layout.getAnchorPoint(); + if (anchorPoint != null) { + drawAttribution(canvas, measure, anchorPoint); + } else { + Bitmap snapshot = mapSnapshot.getBitmap(); + Timber.e("Could not generate attribution for snapshot size: %s x %s." + + " You are required to provide your own attribution for the used sources: %s", + snapshot.getWidth(), snapshot.getHeight(), mapSnapshot.getAttributions()); + } + } + + private void drawAttribution(Canvas canvas, AttributionMeasure measure, PointF anchorPoint) { + canvas.save(); + canvas.translate(anchorPoint.x, anchorPoint.y); + measure.getTextView().draw(canvas); + canvas.restore(); + } + + private TextView createTextView(MapSnapshot mapSnapshot, boolean shortText, float scale) { + int textColor = ResourcesCompat.getColor(context.getResources(), R.color.mapbox_gray_dark, context.getTheme()); + int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + TextView textView = new TextView(context); + textView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT) + ); + textView.setSingleLine(true); + textView.setTextSize(10 * scale); + textView.setTextColor(textColor); + textView.setBackgroundResource(R.drawable.mapbox_rounded_corner); + textView.setText(Html.fromHtml(createAttributionString(mapSnapshot, shortText))); + textView.measure(widthMeasureSpec, heightMeasureSpec); + textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight()); + return textView; } /** - * Draw a logo on the canvas created from the map snapshot. + * Create the attribution string. * - * @param canvas the canvas to draw the bitmap on - * @param original the map snapshot image + * @param mapSnapshot the map snapshot to create the attribution for + * @param shortText indicates if the short variant of the string should be parsed + * @return the parsed attribution string */ - private void addLogo(Canvas canvas, Bitmap original) { - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - float margin = displayMetrics.density * LOGO_MARGIN_DP; - Bitmap logo = createScaledLogo(original); - canvas.drawBitmap(logo, margin, original.getHeight() - logo.getHeight() - margin, null); + private String createAttributionString(MapSnapshot mapSnapshot, boolean shortText) { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(mapSnapshot.getAttributions()) + .withCopyrightSign(false) + .withImproveMap(false) + .build(); + + return attributionParser.createAttributionString(shortText); } /** * Create a scaled logo for a map snapshot. * * @param snapshot the map snapshot where the logo should be placed on - * @return the scaled bitmap logo + * @return the scaled large logo */ - private Bitmap createScaledLogo(Bitmap snapshot) { + private Logo createScaledLogo(@NonNull Bitmap snapshot) { Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, null); float scale = calculateLogoScale(snapshot, logo); Matrix matrix = new Matrix(); matrix.postScale(scale, scale); - return Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true); + Bitmap helmet = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_helmet, null); + Bitmap large = Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true); + Bitmap small = Bitmap.createBitmap(helmet, 0, 0, helmet.getWidth(), helmet.getHeight(), matrix, true); + return new Logo(large, small, scale); } /** * Calculates the scale of the logo, only allow downscaling. * - * @param snapshot the bitmap of the map snapshot - * @param logo the bitmap of the mapbox logo + * @param snapshot the large of the map snapshot + * @param logo the large of the mapbox logo * @return the scale value */ private float calculateLogoScale(Bitmap snapshot, Bitmap logo) { @@ -315,7 +410,14 @@ public class MapSnapshotter { float prefWidth = logo.getWidth() / widthRatio; float prefHeight = logo.getHeight() / heightRatio; float calculatedScale = Math.min(prefWidth / logo.getWidth(), prefHeight / logo.getHeight()) * 2; - return calculatedScale < 1 ? calculatedScale : 1.0f; + if (calculatedScale > 1) { + // don't allow over-scaling + calculatedScale = 1.0f; + } else if (calculatedScale < 0.60f) { + // don't scale to low either + calculatedScale = 0.60f; + } + return calculatedScale; } /** @@ -324,14 +426,17 @@ public class MapSnapshotter { * * @param snapshot the generated snapshot */ - protected void onSnapshotReady(MapSnapshot snapshot) { - if (callback != null) { - if (snapshot.isShowLogo()) { - addOverlay(snapshot); + protected void onSnapshotReady(final MapSnapshot snapshot) { + new Handler().post(new Runnable() { + @Override + public void run() { + if (callback != null) { + addOverlay(snapshot); + callback.onSnapshotReady(snapshot); + reset(); + } } - callback.onSnapshotReady(snapshot); - reset(); - } + }); } /** @@ -364,4 +469,28 @@ public class MapSnapshotter { @Override protected native void finalize() throws Throwable; + + private class Logo { + private Bitmap large; + private Bitmap small; + private float scale; + + public Logo(Bitmap large, Bitmap small, float scale) { + this.large = large; + this.small = small; + this.scale = scale; + } + + public Bitmap getLarge() { + return large; + } + + public Bitmap getSmall() { + return small; + } + + public float getScale() { + return scale; + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/mapbox_logo_helmet.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/mapbox_logo_helmet.png new file mode 100644 index 0000000000..2629afe6a3 Binary files /dev/null and b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/mapbox_logo_helmet.png differ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/mapbox_logo_helmet.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/mapbox_logo_helmet.png new file mode 100644 index 0000000000..34bab3bf6d Binary files /dev/null and b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/mapbox_logo_helmet.png differ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/mapbox_logo_helmet.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/mapbox_logo_helmet.png new file mode 100644 index 0000000000..942d78ec58 Binary files /dev/null and b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/mapbox_logo_helmet.png differ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/mapbox_logo_helmet.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/mapbox_logo_helmet.png new file mode 100644 index 0000000000..947d5a2a30 Binary files /dev/null and b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/mapbox_logo_helmet.png differ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/mapbox_logo_helmet.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/mapbox_logo_helmet.png new file mode 100644 index 0000000000..bec38cedc7 Binary files /dev/null and b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/mapbox_logo_helmet.png differ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_rounded_corner.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_rounded_corner.xml new file mode 100644 index 0000000000..c4dbfb3d80 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_rounded_corner.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml index b51c890e5c..19007f503f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml @@ -1,5 +1,6 @@ + #5F5F5F #7D7F80 #1E8CAB diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java new file mode 100644 index 0000000000..f25cf1b7d8 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java @@ -0,0 +1,309 @@ +package com.mapbox.mapboxsdk.attribution; + +import com.mapbox.mapboxsdk.BuildConfig; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Set; + +import static junit.framework.Assert.assertEquals; + +@RunWith(RobolectricTestRunner.class) +@Config(constants = BuildConfig.class) +public class AttributionParseTest { + + private static final String STREETS_ATTRIBUTION = "© Mapbox © OpenStreetMap Improve this map\n"; + private static final String SATELLITE_ATTRIBUTION = "© Mapbox © OpenStreetMap Improve this map © DigitalGlobe\n"; + + @Test + public void testParseAttributionStringSatellite() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(SATELLITE_ATTRIBUTION) + .build(); + + Set attributionList = attributionParser.getAttributions(); + assertEquals("Size of list should match", 4, attributionList.size()); + + int counter = 0; + for (Attribution attribution : attributionList) { + switch (counter) { + case 0: + assertEquals("URL mapbox should match", "https://www.mapbox.com/about/maps/", attribution.getUrl()); + assertEquals("Title mapbox should match", "© Mapbox", attribution.getTitle()); + break; + case 1: + assertEquals("URL openstreetmap should match", "http://www.openstreetmap.org/about/", attribution.getUrl()); + assertEquals("Title openstreetmap should match", "© OpenStreetMap", attribution.getTitle()); + break; + case 2: + assertEquals("URL improve map should match", "https://www.mapbox.com/map-feedback/", attribution.getUrl()); + assertEquals("Title improve map should match", "Improve this map", attribution.getTitle()); + break; + case 3: + assertEquals("URL digital globe should match", "https://www.digitalglobe.com/", attribution.getUrl()); + assertEquals("Title digital globe should match", "© DigitalGlobe", attribution.getTitle()); + break; + } + counter++; + } + } + + @Test + public void testParseAttributionStringStreets() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION) + .build(); + + Set attributionList = attributionParser.getAttributions(); + assertEquals("Size of list should match", 3, attributionList.size()); + + int counter = 0; + for (Attribution attribution : attributionList) { + switch (counter) { + case 0: + assertEquals("URL mapbox should match", "https://www.mapbox.com/about/maps/", attribution.getUrl()); + assertEquals("Title mapbox should match", "© Mapbox", attribution.getTitle()); + break; + case 1: + assertEquals("URL openstreetmap should match", "http://www.openstreetmap.org/about/", attribution.getUrl()); + assertEquals("Title openstreetmap should match", "© OpenStreetMap", attribution.getTitle()); + break; + case 2: + assertEquals("URL improve map should match", "https://www.mapbox.com/map-feedback/", attribution.getUrl()); + assertEquals("Title improve map should match", "Improve this map", attribution.getTitle()); + break; + } + counter++; + } + } + + @Test + public void testParseAttributionWithoutMapbox() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION) + .withMapboxAttribution(false) + .build(); + + Set attributionList = attributionParser.getAttributions(); + assertEquals("Size of list should match", 2, attributionList.size()); + + int counter = 0; + for (Attribution attribution : attributionList) { + switch (counter) { + case 0: + assertEquals("URL openstreetmap should match", "http://www.openstreetmap.org/about/", attribution.getUrl()); + assertEquals("Title openstreetmap should match", "© OpenStreetMap", attribution.getTitle()); + break; + case 1: + assertEquals("URL improve map should match", "https://www.mapbox.com/map-feedback/", attribution.getUrl()); + assertEquals("Title improve map should match", "Improve this map", attribution.getTitle()); + break; + } + counter++; + } + } + + @Test + public void testParseAttributionArrayString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(new String[] {STREETS_ATTRIBUTION, "", SATELLITE_ATTRIBUTION}) + .build(); + Set attributionList = attributionParser.getAttributions(); + assertEquals("Size of list should match", 4, attributionList.size()); + + int counter = 0; + for (Attribution attribution : attributionList) { + switch (counter) { + case 0: + assertEquals("URL mapbox should match", "https://www.mapbox.com/about/maps/", attribution.getUrl()); + assertEquals("Title mapbox should match", "© Mapbox", attribution.getTitle()); + break; + case 1: + assertEquals("URL openstreetmap should match", "http://www.openstreetmap.org/about/", attribution.getUrl()); + assertEquals("Title openstreetmap should match", "© OpenStreetMap", attribution.getTitle()); + break; + case 2: + assertEquals("URL improve map should match", "https://www.mapbox.com/map-feedback/", attribution.getUrl()); + assertEquals("Title improve map should match", "Improve this map", attribution.getTitle()); + break; + case 3: + assertEquals("URL digital globe should match", "https://www.digitalglobe.com/", attribution.getUrl()); + assertEquals("Title digital globe should match", "© DigitalGlobe", attribution.getTitle()); + break; + } + counter++; + } + } + + @Test + public void testHideImproveThisMapAttributionArrayString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(SATELLITE_ATTRIBUTION) + .withImproveMap(false) + .build(); + Set attributionList = attributionParser.getAttributions(); + assertEquals("Size of list should match", 3, attributionList.size()); + + int counter = 0; + for (Attribution attribution : attributionList) { + switch (counter) { + case 0: + assertEquals("URL mapbox should match", "https://www.mapbox.com/about/maps/", attribution.getUrl()); + assertEquals("Title mapbox should match", "© Mapbox", attribution.getTitle()); + break; + case 1: + assertEquals("URL openstreetmap should match", "http://www.openstreetmap.org/about/", attribution.getUrl()); + assertEquals("Title openstreetmap should match", "© OpenStreetMap", attribution.getTitle()); + break; + case 2: + assertEquals("URL digital globe should match", "https://www.digitalglobe.com/", attribution.getUrl()); + assertEquals("Title digital globe should match", "© DigitalGlobe", attribution.getTitle()); + break; + } + counter++; + } + } + + @Test + public void testParseHideCopyrightAttributionArrayString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION, "", SATELLITE_ATTRIBUTION) + .withCopyrightSign(false) + .build(); + Set attributionList = attributionParser.getAttributions(); + assertEquals("Size of list should match", 4, attributionList.size()); + + int counter = 0; + for (Attribution attribution : attributionList) { + switch (counter) { + case 0: + assertEquals("URL mapbox should match", "https://www.mapbox.com/about/maps/", attribution.getUrl()); + assertEquals("Title mapbox should match", "Mapbox", attribution.getTitle()); + break; + case 1: + assertEquals("URL openstreetmap should match", "http://www.openstreetmap.org/about/", attribution.getUrl()); + assertEquals("Title openstreetmap should match", "OpenStreetMap", attribution.getTitle()); + break; + case 2: + assertEquals("URL improve map should match", "https://www.mapbox.com/map-feedback/", attribution.getUrl()); + assertEquals("Title improve map should match", "Improve this map", attribution.getTitle()); + break; + case 3: + assertEquals("URL digital globe should match", "https://www.digitalglobe.com/", attribution.getUrl()); + assertEquals("Title digital globe should match", "DigitalGlobe", attribution.getTitle()); + break; + } + counter++; + } + } + + @Test + public void testOutputWithoutCopyRightString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION) + .withCopyrightSign(false) + .withImproveMap(false) + .build(); + + assertEquals( + "Attribution string should match", + "© Mapbox / OpenStreetMap", + attributionParser.createAttributionString() + ); + } + + + @Test + public void testOutputWithCopyRightString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION) + .withImproveMap(false) + .build(); + + assertEquals( + "Attribution string should match", + "© Mapbox / © OpenStreetMap", + attributionParser.createAttributionString() + ); + } + + @Test + public void testOutputWithoutCopyRightWithoutMapboxString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION) + .withCopyrightSign(false) + .withImproveMap(false) + .withMapboxAttribution(false) + .build(); + + assertEquals( + "Attribution string should match", + "© OpenStreetMap", + attributionParser.createAttributionString() + ); + } + + @Test + public void testOutputWithCopyRightWithoutMapboxString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION) + .withImproveMap(false) + .withMapboxAttribution(false) + .build(); + + assertEquals( + "Attribution string should match", + "© OpenStreetMap", + attributionParser.createAttributionString() + ); + } + + @Test + public void testOutputSatelliteString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION, SATELLITE_ATTRIBUTION, "blabla", "") + .withImproveMap(false) + .withCopyrightSign(false) + .withMapboxAttribution(false) + .build(); + + assertEquals( + "Attribution string should match", + "© OpenStreetMap / DigitalGlobe", + attributionParser.createAttributionString() + ); + } + + @Test + public void testShortOpenStreetMapString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION, SATELLITE_ATTRIBUTION, "blabla", "") + .withImproveMap(false) + .withCopyrightSign(false) + .withMapboxAttribution(false) + .build(); + + assertEquals( + "Attribution string should match", + "© OSM / DigitalGlobe", + attributionParser.createAttributionString(true) + ); + } + + @Test + public void testShortOpenStreetMapWithoutCopyrightString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION, SATELLITE_ATTRIBUTION, "blabla", "") + .withImproveMap(false) + .withCopyrightSign(false) + .build(); + + assertEquals( + "Attribution string should match", + "© Mapbox / OSM / DigitalGlobe", + attributionParser.createAttributionString(true) + ); + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java index 245786e1d0..c4fe93d200 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java @@ -69,7 +69,7 @@ public class MapSnapshotterActivity extends AppCompatActivity { .withPixelRatio(1) // Optionally the style - .withStyle((column + row) % 2 == 0 ? Style.TRAFFIC_DAY : Style.DARK); + .withStyle((column + row) % 2 == 0 ? Style.MAPBOX_STREETS : Style.DARK); // Optionally the visible region if (row % 2 == 0) { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java index 781e7b6334..b690f18b6a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java @@ -9,14 +9,12 @@ import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.ViewTreeObserver; import android.widget.ImageView; - import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.snapshotter.MapSnapshot; import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter; import com.mapbox.mapboxsdk.testapp.R; - import timber.log.Timber; /** @@ -46,7 +44,7 @@ public class MapSnapshotterMarkerActivity extends AppCompatActivity implements M getApplicationContext(), new MapSnapshotter .Options(Math.min(container.getMeasuredWidth(), 1024), Math.min(container.getMeasuredHeight(), 1024)) - .withStyle(Style.TRAFFIC_DAY) + .withStyle(Style.OUTDOORS) .withCameraPosition(new CameraPosition.Builder().target(new LatLng(52.090737, 5.121420)).zoom(15).build()) ); mapSnapshotter.start(MapSnapshotterMarkerActivity.this); @@ -54,6 +52,12 @@ public class MapSnapshotterMarkerActivity extends AppCompatActivity implements M }); } + @Override + protected void onStop() { + super.onStop(); + mapSnapshotter.cancel(); + } + @Override public void onSnapshotReady(MapSnapshot snapshot) { Timber.i("Snapshot ready"); diff --git a/platform/android/dependencies.gradle b/platform/android/dependencies.gradle index eadf7aea56..0094b79281 100644 --- a/platform/android/dependencies.gradle +++ b/platform/android/dependencies.gradle @@ -25,6 +25,7 @@ ext { // unit test junit : 'junit:junit:4.12', mockito : 'org.mockito:mockito-core:2.10.0', + robolectric : 'org.robolectric:robolectric:3.5.1', // instrumentation test testRunner : "com.android.support.test:runner:${testRunnerVersion}", -- cgit v1.2.1