package info.guardianproject.util; import android.text.Spannable; import android.text.SpannableString; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.text.util.Linkify; import android.text.util.Linkify.TransformFilter; import android.widget.TextView; import java.util.regex.Matcher; import java.util.regex.Pattern; public class LinkifyHelper { private static Pattern bitcoin = Pattern.compile("bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:;@&=]*)?"); private static Pattern geo = Pattern.compile("geo:[-0-9.]+,[-0-9.]+[^ \t\n\"\':]*"); private static Pattern market = Pattern.compile("market://[^ \t\n\"\':,<>]+"); private static Pattern openpgp4fpr = Pattern.compile("openpgp4fpr:[A-Za-z0-9]{8,40}"); private static Pattern xmpp = Pattern.compile("xmpp:[^ \t\n\"\':,<>]+"); private static Pattern twitterHandle = Pattern.compile("@([A-Za-z0-9_-]+)"); private static Pattern hashtag = Pattern.compile("#([A-Za-z0-9_-]+)"); private static Pattern bridge = Pattern.compile("bridge:[^ \t\n\"\':,<>]+"); static TransformFilter returnMatchFilter = new TransformFilter() { @Override public final String transformUrl(final Matcher match, String url) { return match.group(1); } }; /* Right now, if there is no app to handle */ public static void addLinks(TextView text, SpanConverter<URLSpan, ClickableSpan> converter) { Linkify.addLinks(text, Linkify.ALL); Linkify.addLinks(text, geo, null); Linkify.addLinks(text, market, null); Linkify.addLinks(text, openpgp4fpr, null); Linkify.addLinks(text, xmpp, null); Linkify.addLinks(text, twitterHandle, "https://twitter.com/", null, returnMatchFilter); Linkify.addLinks(text, hashtag, "https://twitter.com/hashtag/", null, returnMatchFilter); text.setText(replaceAll(text.getText(), URLSpan.class, converter)); } /** * These are clickable links that will always be safe to click on, whether * or not ChatSecure is using Tor or not. * * @param text */ public static void addTorSafeLinks(TextView text) { Linkify.addLinks(text, bridge, null); } /** * Do not create this static utility class. */ private LinkifyHelper() { } // thanks to @commonsware https://stackoverflow.com/a/11417498 public static <A extends CharacterStyle, B extends CharacterStyle> Spannable replaceAll( CharSequence original, Class<A> sourceType, SpanConverter<A, B> converter) { SpannableString result = new SpannableString(original); A[] spans = result.getSpans(0, result.length(), sourceType); for (A span : spans) { int start = result.getSpanStart(span); int end = result.getSpanEnd(span); int flags = result.getSpanFlags(span); result.removeSpan(span); result.setSpan(converter.convert(span), start, end, flags); } return (result); } public interface SpanConverter<A extends CharacterStyle, B extends CharacterStyle> { B convert(A span); } }