From f0f113bafc49a735a596357a0982e298648f4d48 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 14 Nov 2017 11:13:34 +0100 Subject: [PATCH] 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 --- .../android/MapboxGLAndroidSDK/build.gradle | 5 +- .../mapboxsdk/attribution/Attribution.java | 59 ++++ .../attribution/AttributionLayout.java | 62 ++++ .../attribution/AttributionMeasure.java | 230 +++++++++++++ .../attribution/AttributionParser.java | 257 +++++++++++++++ .../mapbox/mapboxsdk/http/HTTPRequest.java | 1 + .../maps/AttributionDialogManager.java | 77 ++--- .../mapboxsdk/snapshotter/MapSnapshot.java | 2 +- .../mapboxsdk/snapshotter/MapSnapshotter.java | 179 ++++++++-- .../res/drawable-hdpi/mapbox_logo_helmet.png | Bin 0 -> 1650 bytes .../res/drawable-mdpi/mapbox_logo_helmet.png | Bin 0 -> 950 bytes .../res/drawable-xhdpi/mapbox_logo_helmet.png | Bin 0 -> 2184 bytes .../drawable-xxhdpi/mapbox_logo_helmet.png | Bin 0 -> 3389 bytes .../drawable-xxxhdpi/mapbox_logo_helmet.png | Bin 0 -> 4468 bytes .../res/drawable/mapbox_rounded_corner.xml | 10 + .../src/main/res/values/colors.xml | 1 + .../attribution/AttributionParseTest.java | 309 ++++++++++++++++++ .../snapshot/MapSnapshotterActivity.java | 2 +- .../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 bf0af3052..ce886b100 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 000000000..0877b3ab9 --- /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 000000000..b08a8353b --- /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 000000000..667060168 --- /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 000000000..90bb23429 --- /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 32aa25099..9f887ab7c 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 9ccff387f..2956d864e 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()); + attributions.add(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); + 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 eb4f94c42..38c149146 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 5deedc3e6..1c59bb468 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 0000000000000000000000000000000000000000..2629afe6a3834bd47520f8b5829cec1cdfe373fb GIT binary patch literal 1650 zcmV-&295cNP)kwVb{wQR;A2n<-dq9rKcHsZ42(qxHQLY6o+hAm`& z=v)w-8DZnU5gau50cuDLG;9mbkvTUAP*H}%GElIsYlRlDuPyYo`{#8j&$brzBsaPD zymRmQy>svR;vh0{g62E`G5{L^hye%z2mp8ixR~(>fC<1nq9jffGExBB03-#=3xz^X zdU`rW zw0mi3$zm`VM$ex=KiS^iK2Os$05|~{0L-lyfE0iVfJmWG@Xnk$Q&3n~SWXZG8I4)( zcKf}?#>VcGCr_Ff)*S$20Q}Ja*a+Y)05PXdok~4=^ytT2F83uo1I^6L4DZ^t>+;OZ z%qjpgfa~ll;e`0}5|SjjJv}{nd-m-40MAKFOH1aes;ZX0zP@DuGXVY!2Y?Tt6o5#t z*XNg&l|8re0J+_6dr?u*H>0DYZstw=9SXpk05&%?G;FG^t^M?AhtAH{C>YV z+@L7Rc;du~dI0-FjlFyKR$HysUqkKf>+AcDktlE{1t2Dv#DbKRl#Fo7 z=jP^a_xAQK2G$CNBF11aWX;abenFC?oB>nea=EIzy1G($JRXPflvRhr@kAIdE-p@B zrm+M7AxV-D3WcwP128l+G#W(^4-O87bi2cc4_l2!qn^3%p6>2$XYhBWQpuM} zrE>N@r_7cHR_c)eaKl68iLhE}$2-Fk)1-@SXcq@tqY-B5d`rl!^aumQlx z$cQ0~1?VX90?o2Y!lg@>KH+k?Lbj~W=kvC;wLN6gvV&l1IGjCu_HL-dV`5_X(Pll8 zB>f#79Xs0F+dmVF#R*|*gi(vd;sx+9@CNJvoSmJWcAL#M#ooZ<@kAn#h=`;(J3Cv@ z+1Z)ra5z5KYPI`Gk_;=a)9G$7(+9!HC(Q<@Jq)=yba5^78WcLizMl9^CDknwl$&+1-rtN9*Z~6acmZ zAe2fazqz?NCoeCrATBO0DclB{rdOw@r$=-;-Ho88WPwpmJxgyWV*0{O0Y+I_nM9+} z#3v;s#U>^u#!(dIwpc97Mx$}Dt*z}}tJNAvY!rZ70PcnAh}P4b2CC*%raO*{AV&kR wFhOSaX^rW60I;nR%QUY-R(tM)2BUc#5e;F4WnmA}1mc%i^ScUeMg`lLt~}K;-Pri z#U6X`B!!Y@E-?h5rHAdVC!y8WicnCjM$07G1*=J#Kw`}FG81OgMEl^u@ZS6X-uH&@ z`+pX?kGB9f-~`?T-T-Pq6(|6I0e^QAS~{%t0S^Ev6bkjrvOMPTcSzD`OgZ4!gEd2a@}U3P+6~ZXG|q~};%}WUOr=t*KtSiT#=}@Fw%AEfEEa!V zT3Y(tYPHJ1j3h~q*Vfj)tk>(u=7?jl*dp-I01f~%`F#GTcHrgZ2&&QfW5uFZ|!#bcc-VP4Xf2E*zIh{xkDK@jd4XLL?Y0IJz+ z_LUiMeSKZ6*Xvq)^dlo9Ka@(PrvB94<>lpB^S;ih3P2%|NF2Xr2o{UbspJe`C=_~o ze}8{b6h)_bU*{AodY*oys%ms_aIlqDUa$9yY&Kgn(rJ8r+&(ce;h375`e1Bq?EUr& zbGh6fk|eDIKej3=5{bl{&8BKj{(8Nht<`FW?du&pK%JYL`_x=f0CpgtD9Y3SVTtGOPOg_0QGINBa*K`s;WMVL?UrLLf2i+ zZwGvUdvbEJUzX*O;o;$y^Pioay-Xw$rn*aa+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..942d78ec585d996a465ff975b83de401ab84d001 GIT binary patch literal 2184 zcmV;32zU31P)#}% zUrmjY7^mPyU=wuRrbsZXyA38P6#<*oL>v{7w=9hE7#v`E3}qPRacBC^z1s`U9cGaA zB=?VVe&=`2=bZC9=lss^^6-8s2tXJBF#wYF0f341835=2%x$zI-Ui{KX+VL8y2~`M z)>CLtK>YxG0zkG_kd>7sjEjpCL`6jjBO)S105Cp2zWCOg2m~LwoW|wlnXay`CykAbeOIquoiUkA z4q9XfFhm1W?*mE#NCyzi6kND)L3Zreu{@DT^wW*l+S1a}+nSo1n`h3P8D$lJ58xqy zm5qP~(U1ZFC@3fhQmfU!mq;YB*n~7QGxO@?$&=sI)z!_i?Dty$^PYeP0@zD0JF2Rx zK2|D~r368EVH1<>+PBu;&v)uRZ-~Z7e0U$j+J-5BRJ%i2)Xp`fw1!E^UfJgwi zeED)ret!O8Pdl|(EXK*n$yYj^ZpdIT%*>j4ve?kj(ARhG-kqai2>^O$ zOD_cQTL8qNLx+Ou>gxXDiA`#?+833Tm2KCqU7K22S+TQayLRm&Pn|loEjKqe*T=_4 z?80Vkg@uI|>+9=h>7(m50OMNF6aY~G@b>N75~)-g=MEr6QI^We$}dizK0UHt-LSAQ zuY33I{U$y>K9j4?)YQ~~R4V;~&b|iF!M4#r2J-y*^D=iK?RL9esZ@UE4kQ5RbUNG4 zojY&0w6uK9RYxL`#FdwqN731z0SI8v=q~_l^Y!&5^m_dt#A0!{I~~{8*Vh#k6ufjM z3l9(HXJ=;%uUxsZ=uF<*+nW;~AOEQ{nZaPtNu|=yXcKw`pp%)=FaRhlEDUiMvahf2 z5f>x?+`M@+_Tt5hzvPnd+qbXTY&JU`gjg&ND=sb$VaCiXeIUJ&aNxiJcbD8zQc}{w zB@YP+AruNlj#w;CQ!14rE;G1w>sAw28~OS9v8+uVKrpij07*$n$?kx5cX$7%qoc#f zC2wqOjN$Qkp|s6saMe?*)k9peq@*MTdmj=2L;#SQnkp2DL{fJ^&z?Q|$XWUB-MhC8 z4-cngWn~roa2E##X3ovc<&>3`y>l!*d-lw7=i(@0Zop)oPo~#`ZN$t*x!K!NI|Q&&bFan4X@d z)-17zt1lK4Nw97hhdZDejb`4J{P^+X`r6vsMkYTpGID$G-n~Qf^YibtFBA$1iA3_T zi(AjZ_MS`tFflRV*4w4CvvYa9I%Q>LQ&y{W0Km%O!-qS$>K!?9MB?r3&AB&NOr{?| zwOXxlJpilKY8o3Gv$!*$8#iwJdvtWPxuc_lv;K(_Cw{e#qr)(nycp2_{{CfmK;8AG zii?XUHcY*~XkqB8?SpjKjY0{LG6lp?2LXy9~f0(DU zUr|x<4Gqb#t$KLRV*^yJ>QA zvO6p+?4Mju5di36;y)^ts)?c~&K=I<@!;d*4w^}l97#({Q{V$8wOajob#?U=ogDx$!S$}60q_w3VOv|C>pPT^k|N*iAQ?qbX0=-Vc~MbOKPyP~fSIWEKtTtnbO3$= zU`tzD+d_AD_w&@$)Bv$qEZq#qiHV7q#l^+fST<>-As4u!$y~`yhp&SH2nPoTS1w(; z^wMIn^d%-HN=cFo+z8;@+}!JP=g$3O|Ni}t1_uY3lVzG~COrv_699Gsh-C)G=ks|5 z1qHze4<6i>oSd927K==$~R-?I*LioSVz*4pGoK*0(JCeUG) zReFhuiR6wQJILthXklbzq#po`jg2j7wOV6;f4}L)ix+F%LkE4B_t5dsb<;+E--v10 zZ~*=u>i!r1|9xZdM3MKNdqPA5i?I5K7>}MQ=7-oK4QSYCNB;wU&KoYTY$uxl0000< KMNUMnLSTYAF%(k( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..947d5a2a3075ada1740e516251df197a12d2c75e GIT binary patch literal 3389 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 Tusn%@00000NkvXXu0mjf1RbDm literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bec38cedc7cf0159b7d257c4905601317677a188 GIT binary patch literal 4468 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 ZF(A6;@vZtL|Nb8VR28)ps^#IK{{t)HmdXGC literal 0 HcmV?d00001 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 000000000..c4dbfb3d8 --- /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 b51c890e5..19007f503 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 000000000..f25cf1b7d --- /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 245786e1d..c4fe93d20 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 781e7b633..b690f18b6 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 eadf7aea5..0094b7928 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}",