summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java59
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java62
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java230
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java257
4 files changed, 608 insertions, 0 deletions
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<Command> 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.
+ * <p>
+ * Exposes multiple configuration options to manipulate data being parsed.
+ * Use the Options object to build these configurations.
+ * </p>
+ */
+public class AttributionParser {
+
+ private final Set<Attribution> 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<Attribution> 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.
+ * <p>
+ * 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.
+ * </p>
+ */
+ 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();
+ }
+ }
+}