LinkifyCompat.java revision 180f831c5b05bb2a8313d79e577a3714c00c8893
1b29b427fa977e8e13ea104d22b193b2cd8a4a52fSteve Naroff/*
277cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner * Copyright (C) 2016 The Android Open Source Project
377cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner *
477cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner * Licensed under the Apache License, Version 2.0 (the "License");
50bc735ffcfb223c0186419547abaa5c84482663eChris Lattner * you may not use this file except in compliance with the License.
60bc735ffcfb223c0186419547abaa5c84482663eChris Lattner * You may obtain a copy of the License at
777cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner *
877cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner *      http://www.apache.org/licenses/LICENSE-2.0
977cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner *
1077cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner * Unless required by applicable law or agreed to in writing, software
1177cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner * distributed under the License is distributed on an "AS IS" BASIS,
1277cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner * See the License for the specific language governing permissions and
14305c613af6cfc40e519c75d9d2c84c6fa9a841c0Ted Kremenek * limitations under the License.
1577cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner */
1677cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner
172fa67efeaf66a9332c30a026dc1c21bef6c33a6cBenjamin Kramerpackage android.support.v4.text.util;
188599e7a394e7ed44a32dfe64733125e095e3f28cSteve Naroff
193f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Roseimport android.support.annotation.IntDef;
200750618b0beacdc9b0a9e210a661e4746823ced7Chris Lattnerimport android.support.annotation.NonNull;
212fa67efeaf66a9332c30a026dc1c21bef6c33a6cBenjamin Kramerimport android.support.annotation.Nullable;
222fa67efeaf66a9332c30a026dc1c21bef6c33a6cBenjamin Kramerimport android.support.v4.util.PatternsCompat;
2326de4655a38b899e0f0dcef4175032175854d1cfChris Lattnerimport android.text.Spannable;
2455fc873017f10f6f566b182b70f6fc22aefa3464Chandler Carruthimport android.text.SpannableString;
252fa67efeaf66a9332c30a026dc1c21bef6c33a6cBenjamin Kramerimport android.text.Spanned;
262fa67efeaf66a9332c30a026dc1c21bef6c33a6cBenjamin Kramerimport android.text.method.LinkMovementMethod;
272fa67efeaf66a9332c30a026dc1c21bef6c33a6cBenjamin Kramerimport android.text.method.MovementMethod;
286cb7c1a43b0c8f739d1f54b7fdae5ede86033496Benjamin Kramerimport android.text.style.URLSpan;
296cb7c1a43b0c8f739d1f54b7fdae5ede86033496Benjamin Kramerimport android.text.util.Linkify;
30651f13cea278ec967336033dd032faef0e9fc2ecStephen Hinesimport android.text.util.Linkify.MatchFilter;
3172952fc11f80c975492a2a1e0f6e3601c5252e0aFariborz Jahanianimport android.text.util.Linkify.TransformFilter;
32176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hinesimport android.webkit.WebView;
33176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hinesimport android.widget.TextView;
3477cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner
35158ecb9767faf87c2a33df3baec1b160dcc0be84Chris Lattnerimport java.io.UnsupportedEncodingException;
3677cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattnerimport java.net.URLEncoder;
3777cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattnerimport java.lang.annotation.Retention;
38b29b427fa977e8e13ea104d22b193b2cd8a4a52fSteve Naroffimport java.lang.annotation.RetentionPolicy;
39d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanianimport java.util.ArrayList;
4073e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanianimport java.util.Collections;
4159b173d81b05b7d10cec8b06b3fd843230ef628cNico Weberimport java.util.Comparator;
4273e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanianimport java.util.Locale;
4373e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanianimport java.util.regex.Matcher;
4473e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanianimport java.util.regex.Pattern;
4573e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian
4659b173d81b05b7d10cec8b06b3fd843230ef628cNico Weber/**
4773e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian * LinkifyCompat brings in {@code Linkify} improvements for URLs and email addresses to older API
4859b173d81b05b7d10cec8b06b3fd843230ef628cNico Weber * levels.
4973e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian */
5073e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanianpublic final class LinkifyCompat {
5173e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian    private static final String[] EMPTY_STRING = new String[0];
5273e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian
5373e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian    private static final Comparator<LinkSpec>  COMPARATOR = new Comparator<LinkSpec>() {
5473e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian        public final int compare(LinkSpec a, LinkSpec b) {
5573e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian            if (a.start < b.start) {
5673e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian                return -1;
5773e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian            }
5873e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian
5973e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian            if (a.start > b.start) {
6073e437bbe7e9631ad9055b1d50f4ae8564efbdf3Fariborz Jahanian                return 1;
615845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            }
62d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian
632c64b7b9381be4ff62fbdc404ed3f14c8086898dChris Lattner            if (a.end < b.end) {
64d6471f7c1921c7802804ce3ff6fe9768310f72b9David Blaikie                return 1;
654f943c23a47d042c3275e78f2d6015daa650f105Steve Naroff            }
6601c5748c29e75b29cab5fc7d8ad1b173b29c7ecfChris Lattner
6777cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner            if (a.end > b.end) {
68ef177820100ab583b08fd3056e2a5a52ee4b1629Argyrios Kyrtzidis                return -1;
692b2453a7d8fe732561795431f39ceb2b2a832d84Chris Lattner            }
7026de4655a38b899e0f0dcef4175032175854d1cfChris Lattner
715845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            return 0;
725845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        }
73dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian    };
74dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian
75dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian    @IntDef(flag = true, value = { Linkify.WEB_URLS, Linkify.EMAIL_ADDRESSES, Linkify.PHONE_NUMBERS,
76ab10b2ebad09c283ccab0ef043118b3cf0166b56Fariborz Jahanian            Linkify.MAP_ADDRESSES, Linkify.ALL })
77dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian    @Retention(RetentionPolicy.SOURCE)
78dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian    public @interface LinkifyMask {}
79dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian
80dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian    /**
81d82a9ab4540899e24c96a389c5488381c5551c78Steve Naroff     *  Scans the text of the provided Spannable and turns all occurrences
82dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  of the link types indicated in the mask into clickable links.
83dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  If the mask is nonzero, it also removes any existing URLSpans
841eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump     *  attached to the Spannable, to avoid problems if you call it
85dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  repeatedly on the same text.
86dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *
87dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  @param text Spannable whose text is to be marked-up with links
88d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian     *  @param mask Mask to define which kinds of links will be searched.
89dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *
90dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  @return True if at least one link is found and applied.
91dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     */
92dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian    public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) {
93dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian        if (mask == 0) {
94ebf2b56bce1ea6b1b8133c1f0e2131503d229d2dSteve Naroff            return false;
95874e232a0d5e179197de860e6dfa3e99cd42ad30Steve Naroff        }
9680a6a5abbbf0b7c45f535d4e0b0d00f4c3f320eaFariborz Jahanian
9780a6a5abbbf0b7c45f535d4e0b0d00f4c3f320eaFariborz Jahanian        URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
98acb4977dd3b6a678bfbdb80781bfe3c617be3f24Fariborz Jahanian
99ebf2b56bce1ea6b1b8133c1f0e2131503d229d2dSteve Naroff        for (int i = old.length - 1; i >= 0; i--) {
1009bcb5fc1fd48c1f40c6a3b5a59130ebc313b4957Steve Naroff            text.removeSpan(old[i]);
101d314e9e12326c2fd8f140adc4c769d13b483b3f6Fariborz Jahanian        }
102934f276cc5b45e19cd12ebb2d04fd7972a23865cSteve Naroff
1039698464266660346fa2da1bddc3e6404a9819b25Steve Naroff        // Use framework to linkify phone numbers.
104e575359c34a9248c55ec0c03a8fc945f1ee4cb01Benjamin Kramer        boolean frameworkReturn = false;
105dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian        if ((mask & Linkify.PHONE_NUMBERS) != 0) {
106dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian            frameworkReturn = Linkify.addLinks(text, Linkify.PHONE_NUMBERS);
1071eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        }
108dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian
109dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian        ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
110dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian
111dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian        if ((mask & Linkify.WEB_URLS) != 0) {
112dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian            gatherLinks(links, text, PatternsCompat.AUTOLINK_WEB_URL,
113dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian                    new String[] { "http://", "https://", "rtsp://" },
114dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian                    Linkify.sUrlMatchFilter, null);
115dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian        }
116dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian
117dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian        if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
118dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian            gatherLinks(links, text, PatternsCompat.AUTOLINK_EMAIL_ADDRESS,
119dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian                    new String[] { "mailto:" },
120dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian                    null, null);
12154055232a5ddb9529726e934301b125cb720a273Steve Naroff        }
12254055232a5ddb9529726e934301b125cb720a273Steve Naroff
1235f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner        if ((mask & Linkify.MAP_ADDRESSES) != 0) {
1245f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner            gatherMapLinks(links, text);
125f4b88a45902af1802a1cb42ba48b1c474474f228John McCall        }
1265e49b2f3e0bbc583076fe8af00dff06bcba06dafFariborz Jahanian
127f4b88a45902af1802a1cb42ba48b1c474474f228John McCall        pruneOverlaps(links, text);
1281eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump
12954055232a5ddb9529726e934301b125cb720a273Steve Naroff        if (links.size() == 0) {
1305f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner            return false;
131bab71685568085b635f077ee5720d22dffab84beFariborz Jahanian        }
1325f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner
133bab71685568085b635f077ee5720d22dffab84beFariborz Jahanian        for (LinkSpec link: links) {
134a73165e47aefbea60312d284343660c9c962c9c3Fariborz Jahanian            if (link.frameworkAddedSpan == null) {
13554055232a5ddb9529726e934301b125cb720a273Steve Naroff                applyLink(link.url, link.start, link.end, text);
1366cb6eb4c792b504ad652d9230640656852e18ee9Fariborz Jahanian            }
1376cb6eb4c792b504ad652d9230640656852e18ee9Fariborz Jahanian        }
13854055232a5ddb9529726e934301b125cb720a273Steve Naroff
13954055232a5ddb9529726e934301b125cb720a273Steve Naroff        return true;
1404c3580e5f9d1804fa08fecca76ad5089bc9965feSteve Naroff    }
1414c3580e5f9d1804fa08fecca76ad5089bc9965feSteve Naroff
1424c3580e5f9d1804fa08fecca76ad5089bc9965feSteve Naroff    /**
1434c3580e5f9d1804fa08fecca76ad5089bc9965feSteve Naroff     *  Scans the text of the provided TextView and turns all occurrences of
14415f081de2c8ac7deadf5d938b458b20732230cd9Steve Naroff     *  the link types indicated in the mask into clickable links.  If matches
145dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  are found the movement method for the TextView is set to
146dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  LinkMovementMethod.
147dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *
148dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  @param text TextView whose text is to be marked-up with links
149dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian     *  @param mask Mask to define which kinds of links will be searched.
150b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *
1514b9c2d235fb9449e249d74f48ecfec601650de93John McCall     *  @return True if at least one link is found and applied.
1524b9c2d235fb9449e249d74f48ecfec601650de93John McCall     */
1534b9c2d235fb9449e249d74f48ecfec601650de93John McCall    public static final boolean addLinks(@NonNull TextView text, @LinkifyMask int mask) {
1545845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        if (mask == 0) {
1554b9c2d235fb9449e249d74f48ecfec601650de93John McCall            return false;
1564b9c2d235fb9449e249d74f48ecfec601650de93John McCall        }
1574b9c2d235fb9449e249d74f48ecfec601650de93John McCall
1584b9c2d235fb9449e249d74f48ecfec601650de93John McCall        CharSequence t = text.getText();
1594b9c2d235fb9449e249d74f48ecfec601650de93John McCall
1604967a710c84587c654b56c828382219c3937dacbPirama Arumuga Nainar        if (t instanceof Spannable) {
1614b9c2d235fb9449e249d74f48ecfec601650de93John McCall            if (addLinks((Spannable) t, mask)) {
1624b9c2d235fb9449e249d74f48ecfec601650de93John McCall                addLinkMovementMethod(text);
1634b9c2d235fb9449e249d74f48ecfec601650de93John McCall                return true;
1644b9c2d235fb9449e249d74f48ecfec601650de93John McCall            }
1654967a710c84587c654b56c828382219c3937dacbPirama Arumuga Nainar
166d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian            return false;
1671eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        } else {
16877cd2a0b6eea81cc393b4c9e2941ec31fa09fdbeChris Lattner            SpannableString s = SpannableString.valueOf(t);
169f04da137288c48879a86e9e1d4014db8e28dbae1Chris Lattner
170651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines            if (addLinks(s, mask)) {
17195ed7784a335aca53b0c6e952cf31a4cfb633360Fariborz Jahanian                addLinkMovementMethod(text);
172375bb1413c041055262c8a416f20d10474a5eda9Douglas Gregor                text.setText(s);
173375bb1413c041055262c8a416f20d10474a5eda9Douglas Gregor
174375bb1413c041055262c8a416f20d10474a5eda9Douglas Gregor                return true;
175375bb1413c041055262c8a416f20d10474a5eda9Douglas Gregor            }
176375bb1413c041055262c8a416f20d10474a5eda9Douglas Gregor
17795ed7784a335aca53b0c6e952cf31a4cfb633360Fariborz Jahanian            return false;
178375bb1413c041055262c8a416f20d10474a5eda9Douglas Gregor        }
179bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor    }
180bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor
181bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor    /**
182bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor     *  Applies a regex to the text of a TextView turning the matches into
183bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor     *  links.  If links are found then UrlSpans are applied to the link
184bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor     *  text match areas, and the movement method for the text is changed
185bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor     *  to LinkMovementMethod.
186682bf92db408a6cbc3d37b5496a99b6ef85041ecChris Lattner     *
18795ed7784a335aca53b0c6e952cf31a4cfb633360Fariborz Jahanian     *  @param text         TextView whose text is to be marked-up with links
18888c2596edc8eb475e20f6033de1ea01669695a0cArgyrios Kyrtzidis     *  @param pattern      Regex pattern to be used for finding links
189682bf92db408a6cbc3d37b5496a99b6ef85041ecChris Lattner     *  @param scheme       URL scheme string (eg <code>http://</code>) to be
1904967a710c84587c654b56c828382219c3937dacbPirama Arumuga Nainar     *                      prepended to the links that do not start with this scheme.
191682bf92db408a6cbc3d37b5496a99b6ef85041ecChris Lattner     */
1922c64b7b9381be4ff62fbdc404ed3f14c8086898dChris Lattner    public static final void addLinks(@NonNull TextView text, @NonNull Pattern pattern,
1935f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner            @Nullable String scheme) {
194d6471f7c1921c7802804ce3ff6fe9768310f72b9David Blaikie        addLinks(text, pattern, scheme, null, null, null);
195c6d656e2b0491f224ddd48507627b51d630d749aEli Friedman    }
196e452e0ffc81c6c3d79680f552f2623e6cf0956d7Ted Kremenek
19758878f85ab89b13e9eea4af3ccf055e42c557bc8Pirama Arumuga Nainar    /**
1981eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump     *  Applies a regex to the text of a TextView turning the matches into
199651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines     *  links.  If links are found then UrlSpans are applied to the link
2001eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump     *  text match areas, and the movement method for the text is changed
20188906cddbb1d5b3a868eeeec6cb170befc829c2fFariborz Jahanian     *  to LinkMovementMethod.
202176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines     *
203dcbc5b0b0722282a0fdd829359fe0d7e22adb882Chris Lattner     *  @param text         TextView whose text is to be marked-up with links
204b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  @param pattern      Regex pattern to be used for finding links
205b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  @param scheme       URL scheme string (eg <code>http://</code>) to be
206c568f1e98938584c0ef0b12ae5018ff7d90a4072Stephen Hines     *                      prepended to the links that do not start with this scheme.
207176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines     *  @param matchFilter  The filter that is used to allow the client code
208176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines     *                      additional control over which pattern matches are
209176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines     *                      to be converted into links.
210176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines     */
211176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines    public static final void addLinks(@NonNull TextView text, @NonNull Pattern pattern,
2124b9c2d235fb9449e249d74f48ecfec601650de93John McCall            @Nullable String scheme, @Nullable MatchFilter matchFilter,
2134b9c2d235fb9449e249d74f48ecfec601650de93John McCall            @Nullable TransformFilter transformFilter) {
2144b9c2d235fb9449e249d74f48ecfec601650de93John McCall        addLinks(text, pattern, scheme, null, matchFilter, transformFilter);
2157e7492442f32e3abbe246f6bb35568b044c1188bNick Lewycky    }
216b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff
217b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff    /**
218b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  Applies a regex to the text of a TextView turning the matches into
219b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  links.  If links are found then UrlSpans are applied to the link
220b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  text match areas, and the movement method for the text is changed
221b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  to LinkMovementMethod.
222b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *
223b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  @param text TextView whose text is to be marked-up with links.
224b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  @param pattern Regex pattern to be used for finding links.
2256bcf27bb9a4b5c3f79cb44c0e4654a6d7619ad89Stephen Hines     *  @param defaultScheme The default scheme to be prepended to links if the link does not
226b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *                       start with one of the <code>schemes</code> given.
227b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  @param schemes Array of schemes (eg <code>http://</code>) to check if the link found
228b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *                 contains a scheme. Passing a null or empty value means prepend defaultScheme
229d7407dc92c7d19cafce429e7e1cf9819d3fc0b92Daniel Dunbar     *                 to all links.
230b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  @param matchFilter  The filter that is used to allow the client code additional control
231b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *                      over which pattern matches are to be converted into links.
232b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     *  @param transformFilter Filter to allow the client code to update the link found.
233b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff     */
234b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff    public static final void addLinks(@NonNull TextView text, @NonNull Pattern pattern,
235b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff            @Nullable  String defaultScheme, @Nullable String[] schemes,
236b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff            @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter) {
237b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff        SpannableString spannable = SpannableString.valueOf(text.getText());
238b619d957b020744bb6bfdd1cef8169d8042df43eSteve Naroff
2395f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner        boolean linksAdded = addLinks(spannable, pattern, defaultScheme, schemes, matchFilter,
240ba92b2ed976e29ea05f0f5afabaf9488c1096bebSteve Naroff                transformFilter);
241aadaf78d65daef3ac1b45e4ad6136ce859962fe2Chris Lattner        if (linksAdded) {
242d999b3736ce2646ae0711416b421d906298764dbBenjamin Kramer            text.setText(spannable);
243f3dd57e5378bcf5fc9f05832e92479a535d9cd8aChris Lattner            addLinkMovementMethod(text);
244f3dd57e5378bcf5fc9f05832e92479a535d9cd8aChris Lattner        }
2451eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump    }
246f3dd57e5378bcf5fc9f05832e92479a535d9cd8aChris Lattner
247f3dd57e5378bcf5fc9f05832e92479a535d9cd8aChris Lattner    /**
2481eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump     *  Applies a regex to a Spannable turning the matches into
249aadaf78d65daef3ac1b45e4ad6136ce859962fe2Chris Lattner     *  links.
2505f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner     *
251aadaf78d65daef3ac1b45e4ad6136ce859962fe2Chris Lattner     *  @param text         Spannable whose text is to be marked-up with links
252d999b3736ce2646ae0711416b421d906298764dbBenjamin Kramer     *  @param pattern      Regex pattern to be used for finding links
253aadaf78d65daef3ac1b45e4ad6136ce859962fe2Chris Lattner     *  @param scheme       URL scheme string (eg <code>http://</code>) to be
254aadaf78d65daef3ac1b45e4ad6136ce859962fe2Chris Lattner     *                      prepended to the links that do not start with this scheme.
2551eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump     */
256aadaf78d65daef3ac1b45e4ad6136ce859962fe2Chris Lattner    public static final boolean addLinks(@NonNull Spannable text, @NonNull Pattern pattern,
257aadaf78d65daef3ac1b45e4ad6136ce859962fe2Chris Lattner            @Nullable String scheme) {
2581eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        return addLinks(text, pattern, scheme, null, null, null);
259f04da137288c48879a86e9e1d4014db8e28dbae1Chris Lattner    }
2605845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
261452b899c9f5fbcb8ddd130375b7982bed6b4d93fFariborz Jahanian    /**
26295ed7784a335aca53b0c6e952cf31a4cfb633360Fariborz Jahanian     * Applies a regex to a Spannable turning the matches into
2636b9240e058bf3451685df73fc8ce181b3046e92bCraig Topper     * links.
264375bb1413c041055262c8a416f20d10474a5eda9Douglas Gregor     *
265a5e2b23b594a03a5ab846ec9433a720cb3f3cc3aFariborz Jahanian     * @param spannable    Spannable whose text is to be marked-up with links
2665845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian     * @param pattern      Regex pattern to be used for finding links
267a0876e88aff05e8fc0492d216c17bff16de31a37Steve Naroff     * @param scheme       URL scheme string (eg <code>http://</code>) to be
268a0876e88aff05e8fc0492d216c17bff16de31a37Steve Naroff     *                     prepended to the links that do not start with this scheme.
269a0876e88aff05e8fc0492d216c17bff16de31a37Steve Naroff     * @param matchFilter  The filter that is used to allow the client code
270a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek     *                     additional control over which pattern matches are
2714afa39deaa245592977136d367251ee2c173dd8dDouglas Gregor     *                     to be converted into links.
2722d8c1fdef9658d7c3d56bcfd5533188f6c8b6bf5Fariborz Jahanian     * @param transformFilter Filter to allow the client code to update the link found.
2732d8c1fdef9658d7c3d56bcfd5533188f6c8b6bf5Fariborz Jahanian     *
2747c63fddad600e710042fa018768807bd04eaa233Fariborz Jahanian     * @return True if at least one link is found and applied.
2757c63fddad600e710042fa018768807bd04eaa233Fariborz Jahanian     */
276a73165e47aefbea60312d284343660c9c962c9c3Fariborz Jahanian    public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
2771e8011e2fcddc4a638d210ec2dcd21adcf7b1763Fariborz Jahanian            @Nullable String scheme, @Nullable MatchFilter matchFilter,
278a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            @Nullable TransformFilter transformFilter) {
279a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        return addLinks(spannable, pattern, scheme, null, matchFilter,
280bd9482d859a74bf2c45ef8b8aedec61c0e1c8374Douglas Gregor                transformFilter);
2816b9240e058bf3451685df73fc8ce181b3046e92bCraig Topper    }
282a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek
2836327e0d55c590b3c2766fa76ef1db241a0467df2Steve Naroff    /**
28409b266eb2a014b7af4dc06126c47b7629030ce09Steve Naroff     * Applies a regex to a Spannable turning the matches into links.
285fa297fb29b38991c537a0ae90ff595102dcd21a9Daniel Dunbar     *
286fa297fb29b38991c537a0ae90ff595102dcd21a9Daniel Dunbar     * @param spannable Spannable whose text is to be marked-up with links.
287abfd83e74ca8a7553e375dd4631d2570f33648b4Fariborz Jahanian     * @param pattern Regex pattern to be used for finding links.
288a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek     * @param defaultScheme The default scheme to be prepended to links if the link does not
2894c863ef92c2b74572090da245c87e1487b0b596cFariborz Jahanian     *                      start with one of the <code>schemes</code> given.
2904f95b750534f2111f28434b282bcbd5656002816Steve Naroff     * @param schemes Array of schemes (eg <code>http://</code>) to check if the link found
2915845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian     *                contains a scheme. Passing a null or empty value means prepend defaultScheme
292f04da137288c48879a86e9e1d4014db8e28dbae1Chris Lattner     *                to all links.
293f3473a7e48dfaaa88f58dd9304856e16754f9b1cSteve Naroff     * @param matchFilter  The filter that is used to allow the client code additional control
294e64b7771dca65d737dfc49b6137bd69fc1ff6bd2Chris Lattner     *                     over which pattern matches are to be converted into links.
2954b9c2d235fb9449e249d74f48ecfec601650de93John McCall     * @param transformFilter Filter to allow the client code to update the link found.
2964b9c2d235fb9449e249d74f48ecfec601650de93John McCall     *
297b42f8415bcfb84c208fd577458ce1bbc2cd800feSteve Naroff     * @return True if at least one link is found and applied.
298e64b7771dca65d737dfc49b6137bd69fc1ff6bd2Chris Lattner     */
299beaf299a2701c5559a4e5d76b0c40f805afb8e6aSteve Naroff    public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
30036ee2cb3247a662b6049f9cc097ba5cf9c0bb2b5Fariborz Jahanian            @Nullable  String defaultScheme, @Nullable String[] schemes,
301b85e77af052ea495aa47edcce3b1ec8887e53873Steve Naroff            @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter) {
302b85e77af052ea495aa47edcce3b1ec8887e53873Steve Naroff        final String[] schemesCopy;
303a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        if (defaultScheme == null) defaultScheme = "";
304a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian        if (schemes == null || schemes.length < 1) {
305a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            schemes = EMPTY_STRING;
306338d1e2ced8037b71d91fb319631846917d0cedaChris Lattner        }
307338d1e2ced8037b71d91fb319631846917d0cedaChris Lattner
308e8d1c0579404442a87818506bb0e742d0f52d5bfFariborz Jahanian        schemesCopy = new String[schemes.length + 1];
309e8d1c0579404442a87818506bb0e742d0f52d5bfFariborz Jahanian        schemesCopy[0] = defaultScheme.toLowerCase(Locale.ROOT);
3105845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        for (int index = 0; index < schemes.length; index++) {
311d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian            String scheme = schemes[index];
31254055232a5ddb9529726e934301b125cb720a273Steve Naroff            schemesCopy[index + 1] = (scheme == null) ? "" : scheme.toLowerCase(Locale.ROOT);
3131eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        }
3145845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3151eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        boolean hasMatches = false;
31654055232a5ddb9529726e934301b125cb720a273Steve Naroff        Matcher m = pattern.matcher(spannable);
31752b08f2c8a65c059cb2123fa0fd6317b829416deFariborz Jahanian
318f4b88a45902af1802a1cb42ba48b1c474474f228John McCall        while (m.find()) {
3196cb6eb4c792b504ad652d9230640656852e18ee9Fariborz Jahanian            int start = m.start();
32054055232a5ddb9529726e934301b125cb720a273Steve Naroff            int end = m.end();
3215845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            boolean allowed = true;
3225845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3235845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            if (matchFilter != null) {
324651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines                allowed = matchFilter.acceptMatch(spannable, start, end);
325176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines            }
326651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines
327d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian            if (allowed) {
328d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian                String url = makeUrl(m.group(0), schemesCopy, m, transformFilter);
329d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian
330d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian                applyLink(url, start, end, spannable);
331d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian                hasMatches = true;
332d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian            }
333d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian        }
334d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian
335d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian        return hasMatches;
336d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian    }
337d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian
338d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian    private static void addLinkMovementMethod(@NonNull TextView t) {
339d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian        MovementMethod m = t.getMovementMethod();
340d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian
341d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian        if ((m == null) || !(m instanceof LinkMovementMethod)) {
342d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian            if (t.getLinksClickable()) {
343d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian                t.setMovementMethod(LinkMovementMethod.getInstance());
344d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian            }
345d5c3fa29c6a06ed9c74e03b4a96f786cbc156b7cFariborz Jahanian        }
3465845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian    }
34748d798ce32447607144db70a484cdb99c1180663Benjamin Kramer
3485845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian    private static String makeUrl(@NonNull String url, @NonNull String[] prefixes,
3495845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            Matcher matcher, @Nullable Linkify.TransformFilter filter) {
35087d948ecccffea9e9e37d0d053b246e2d6d6c47bPirama Arumuga Nainar        if (filter != null) {
3515845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            url = filter.transformUrl(matcher, url);
3525845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        }
3539fc5b007726ef8a2fa923a95f047a51477704121Fariborz Jahanian
3549fc5b007726ef8a2fa923a95f047a51477704121Fariborz Jahanian        boolean hasPrefix = false;
3559fc5b007726ef8a2fa923a95f047a51477704121Fariborz Jahanian
3569fc5b007726ef8a2fa923a95f047a51477704121Fariborz Jahanian        for (int i = 0; i < prefixes.length; i++) {
3579fc5b007726ef8a2fa923a95f047a51477704121Fariborz Jahanian            if (url.regionMatches(true, 0, prefixes[i], 0, prefixes[i].length())) {
3589fc5b007726ef8a2fa923a95f047a51477704121Fariborz Jahanian                hasPrefix = true;
3595845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3605845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                // Fix capitalization if necessary
3615845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                if (!url.regionMatches(false, 0, prefixes[i], 0, prefixes[i].length())) {
3625845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                    url = prefixes[i] + url.substring(prefixes[i].length());
3635845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                }
3645845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3655845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                break;
3665845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            }
3675845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        }
3685845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3695845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        if (!hasPrefix && prefixes.length > 0) {
3705845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian            url = prefixes[0] + url;
3715845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        }
3725845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
373e575359c34a9248c55ec0c03a8fc945f1ee4cb01Benjamin Kramer        return url;
3745845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian    }
3755845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3761eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump    private static void gatherLinks(ArrayList<LinkSpec> links,
3775f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner            Spannable s, Pattern pattern, String[] schemes,
3781eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump            Linkify.MatchFilter matchFilter, Linkify.TransformFilter transformFilter) {
3795f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner        Matcher m = pattern.matcher(s);
38001aec11c8cace2b9b22531627970d8bbdbac0c1cSteve Naroff
38101aec11c8cace2b9b22531627970d8bbdbac0c1cSteve Naroff        while (m.find()) {
38201aec11c8cace2b9b22531627970d8bbdbac0c1cSteve Naroff            int start = m.start();
38301aec11c8cace2b9b22531627970d8bbdbac0c1cSteve Naroff            int end = m.end();
3845f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner
38501aec11c8cace2b9b22531627970d8bbdbac0c1cSteve Naroff            if (matchFilter == null || matchFilter.acceptMatch(s, start, end)) {
3868a9e170efd32855377bc7fc5f7ea431ec4f8802eFariborz Jahanian                LinkSpec spec = new LinkSpec();
38754055232a5ddb9529726e934301b125cb720a273Steve Naroff                String url = makeUrl(m.group(0), schemes, m, transformFilter);
3885f9e272e632e951b1efe824cd16acb4d96077930Chris Lattner
3895845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                spec.url = url;
3905845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                spec.start = start;
3916b9240e058bf3451685df73fc8ce181b3046e92bCraig Topper                spec.end = end;
3925845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3935845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                links.add(spec);
394dd8079c54b0030152c373541f6b0d3d7983ef18cFariborz Jahanian            }
3955845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        }
3965845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian    }
3975845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
3985845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian    private static void applyLink(String url, int start, int end, Spannable text) {
3995845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        URLSpan span = new URLSpan(url);
4001eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump
4015845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian        text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
40254055232a5ddb9529726e934301b125cb720a273Steve Naroff    }
40354055232a5ddb9529726e934301b125cb720a273Steve Naroff
4046b9240e058bf3451685df73fc8ce181b3046e92bCraig Topper    private static final void gatherMapLinks(ArrayList<LinkSpec> links, Spannable s) {
4056b9240e058bf3451685df73fc8ce181b3046e92bCraig Topper        String string = s.toString();
406176edba5311f6eff0cad2631449885ddf4fbc9eaStephen Hines        String address;
4071eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        int base = 0;
40854055232a5ddb9529726e934301b125cb720a273Steve Naroff
40954055232a5ddb9529726e934301b125cb720a273Steve Naroff        try {
4108189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek            while ((address = WebView.findAddress(string)) != null) {
4118189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek                int start = string.indexOf(address);
4128189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
4131eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump                if (start < 0) {
4144fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                    break;
4154fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                }
4164fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian
4174fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                LinkSpec spec = new LinkSpec();
4184fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                int length = address.length();
4194fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                int end = start + length;
4204fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian
4214fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                spec.start = base + start;
4224fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                spec.end = base + end;
4234fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                string = string.substring(end);
4244fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian                base += end;
4254fc8453dd02d015b1161d83a5740632617aedd12Fariborz Jahanian
4265845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                String encodedAddress = null;
4275845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
4285845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                try {
4295845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                    encodedAddress = URLEncoder.encode(address,"UTF-8");
4305845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                } catch (UnsupportedEncodingException e) {
4315845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                    continue;
4328188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian                }
4338188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian
4348188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian                spec.url = "geo:0,0?q=" + encodedAddress;
4358188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian                links.add(spec);
4368188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian            }
4378188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian        } catch (UnsupportedOperationException e) {
4383a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian            // findAddress may fail with an unsupported exception on platforms without a WebView.
4393a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian            // In this case, we will not append anything to the links variable: it would have died
4403a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian            // in WebView.findAddress.
4413a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian            return;
4423a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian        }
4433a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian    }
4443a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian
4453a448fb539f66b204a6119946973d29fa5547574Fariborz Jahanian    private static final void pruneOverlaps(ArrayList<LinkSpec> links, Spannable text) {
4468188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian        // Append spans added by framework
4478188e5f6367714e0eda2bdf9a9710131cb15bbfbFariborz Jahanian        URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
44854055232a5ddb9529726e934301b125cb720a273Steve Naroff        for (int i = 0; i < urlSpans.length; i++) {
44954055232a5ddb9529726e934301b125cb720a273Steve Naroff            LinkSpec spec = new LinkSpec();
45054055232a5ddb9529726e934301b125cb720a273Steve Naroff            spec.frameworkAddedSpan = urlSpans[i];
45154055232a5ddb9529726e934301b125cb720a273Steve Naroff            spec.start = text.getSpanStart(urlSpans[i]);
4521eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump            spec.end = text.getSpanEnd(urlSpans[i]);
45354055232a5ddb9529726e934301b125cb720a273Steve Naroff            links.add(spec);
4541eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        }
45554055232a5ddb9529726e934301b125cb720a273Steve Naroff
45654055232a5ddb9529726e934301b125cb720a273Steve Naroff        Collections.sort(links, COMPARATOR);
45754055232a5ddb9529726e934301b125cb720a273Steve Naroff
4581eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump        int len = links.size();
4596217b80b7a1379b74cced1c076338262c3c980b3Ted Kremenek        int i = 0;
4601eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump
461d1b3c2dd5bc1f3103bee6137957aa7c5f8f2f0bcSteve Naroff        while (i < len - 1) {
46254055232a5ddb9529726e934301b125cb720a273Steve Naroff            LinkSpec a = links.get(i);
46354055232a5ddb9529726e934301b125cb720a273Steve Naroff            LinkSpec b = links.get(i + 1);
46454055232a5ddb9529726e934301b125cb720a273Steve Naroff            int remove = -1;
46554055232a5ddb9529726e934301b125cb720a273Steve Naroff
46654055232a5ddb9529726e934301b125cb720a273Steve Naroff            if ((a.start <= b.start) && (a.end > b.start)) {
467e985d01390a828d9ea679c26c711d5509fd27709Fariborz Jahanian                if (b.end <= a.end) {
4688189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek                    remove = i + 1;
4698189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek                } else if ((a.end - a.start) > (b.end - b.start)) {
4705845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian                    remove = i + 1;
471621edce9cd7d4e06979daf911cc306350619f33bSteve Naroff                } else if ((a.end - a.start) < (b.end - b.start)) {
4721eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump                    remove = i;
473621edce9cd7d4e06979daf911cc306350619f33bSteve Naroff                }
474621edce9cd7d4e06979daf911cc306350619f33bSteve Naroff
475621edce9cd7d4e06979daf911cc306350619f33bSteve Naroff                if (remove != -1) {
476621edce9cd7d4e06979daf911cc306350619f33bSteve Naroff                    URLSpan span = links.get(remove).frameworkAddedSpan;
477621edce9cd7d4e06979daf911cc306350619f33bSteve Naroff                    if (span != null) {
478621edce9cd7d4e06979daf911cc306350619f33bSteve Naroff                        text.removeSpan(span);
479e23cf437fe76b1ed02d63c3f61b456fd48a915f5John McCall                    }
480e23cf437fe76b1ed02d63c3f61b456fd48a915f5John McCall                    links.remove(remove);
481bea522ff43a3f11c7a2bc7949119dbb9fce19e39Jordan Rose                    len--;
482e23cf437fe76b1ed02d63c3f61b456fd48a915f5John McCall                    continue;
48388914801a4d73e321c6f74f97df7d7b11c298bc6Fariborz Jahanian                }
48488914801a4d73e321c6f74f97df7d7b11c298bc6Fariborz Jahanian
485e23cf437fe76b1ed02d63c3f61b456fd48a915f5John McCall            }
486e23cf437fe76b1ed02d63c3f61b456fd48a915f5John McCall
487bea522ff43a3f11c7a2bc7949119dbb9fce19e39Jordan Rose            i++;
488e23cf437fe76b1ed02d63c3f61b456fd48a915f5John McCall        }
4899d125033a9853f3b572a4c9e2f9e2d4e5e346973John McCall    }
4905845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian
4915845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian    /**
4925845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian     * Do not create this static utility class.
4935845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian     */
4946bcf27bb9a4b5c3f79cb44c0e4654a6d7619ad89Stephen Hines    private LinkifyCompat() {}
4956bcf27bb9a4b5c3f79cb44c0e4654a6d7619ad89Stephen Hines
4965845717880d1b0ecaf68c58093681eb18ace9237Fariborz Jahanian    private static class LinkSpec {
497651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines        URLSpan frameworkAddedSpan;
498651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines        String url;
499651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines        int start;
500651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines        int end;
501651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines    }
502651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines}
503651f13cea278ec967336033dd032faef0e9fc2ecStephen Hines