1

I have a text containing us phone numbers. I would like to make them clickable whatever is the phone language. I investigated how the autolink worked and found the Linkify.addLinks method I tried to use on a custom TextView.

public class PhoneNumberLinkTextView extends android.support.v7.widget.AppCompatTextView {

    public PhoneNumberLinkTextView(Context context) {
        super(context);
    }

    public PhoneNumberLinkTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public PhoneNumberLinkTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setUSNumberText(CharSequence text) {
        SpannableStringBuilder spanText = new SpannableStringBuilder(text);
        if (addLinks(spanText)) {
            setText(spanText);
        } else {
            setText(text);
        }
    }

    public boolean addLinks(@NonNull SpannableStringBuilder text) {
        ArrayList<LinkSpec> links = new ArrayList<>();

        gatherTelLinks(links, text);

        if (links.isEmpty()) {
            return false;
        }

        Object[] spans = text.getSpans(0, text.length(), Object.class);
        final int count = spans.length;
        for (int i = 0; i < count; i++) {
            text.removeSpan(spans[i]);
        }

        for (LinkSpec link: links) {
            applyLink(link.url, link.start, link.end, text);
        }

        return true;
    }

    private void gatherTelLinks(ArrayList<LinkSpec> links, Spannable s) {
        PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
        Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
                Locale.US.getCountry(), PhoneNumberUtil.Leniency.POSSIBLE, Long.MAX_VALUE);
        for (PhoneNumberMatch match : matches) {
            LinkSpec spec = new LinkSpec();
            spec.url = "tel:" + normalizeNumber(match.rawString());
            spec.start = match.start();
            spec.end = match.end();
            links.add(spec);
        }
    }

    private void applyLink(String url, int start, int end, Spannable text) {
        URLSpan span = new URLSpan (url);

        text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    /**
     * Normalize a phone number by removing the characters other than digits. If
     * the given number has keypad letters, the letters will be converted to
     * digits first.
     *
     * @param phoneNumber the number to be normalized.
     * @return the normalized number.
     */
    public String normalizeNumber(String phoneNumber) {
        if (TextUtils.isEmpty(phoneNumber)) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        int len = phoneNumber.length();
        for (int i = 0; i < len; i++) {
            char c = phoneNumber.charAt(i);
            // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
            int digit = Character.digit(c, 10);
            if (digit != -1) {
                sb.append(digit);
            } else if (sb.length() == 0 && c == '+') {
                sb.append(c);
            } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
                return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
            }
        }
        return sb.toString();
    }

    class LinkSpec {
        String url;
        int start;
        int end;
    }
}

This code is currently visually working. My US number is formatted as I expect it to be but my phone number is not clickable.

I then tried to add a setMovementMethod(LinkMovementMethod.getInstance()) after my setText() but this time I lost my US number formatted as a phone number.

Does anyone know how I can achieve what I'm trying to do ?

Tibo
  • 523
  • 1
  • 8
  • 20

2 Answers2

0

I ended solving my problem by replacing URLSpan by a custom class extending ClickableSpan.

private class USNumberSpan extends ClickableSpan {

        private String url;

        USNumberSpan(String url) {
            this.url = url;
        }

        @Override
        public void onClick(View widget) {
            Uri uri = Uri.parse(url);
            Context context = widget.getContext();
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
            try {
                context.startActivity(intent);
            } catch (ActivityNotFoundException e) {
                Log.w("URLSpan", "Activity was not found for intent, " + intent.toString(), e);
            }
        }
    }

I did not find out why this is working whereas URLSpan is not but I'm glad it worked.

Tibo
  • 523
  • 1
  • 8
  • 20
0

I've found a Kotlin answer (by the user Iliya Mashin) with a Pattern for any type of numbers on this link: android:autoLink for phone numbers doesn't always work

I adapted it for Java and specified at least 4 numbers in the end (so it won't linkify some zipcodes ending with 3 numbers "xxxxx-xxx"), so, if you don't want this specific limitation, just remove the "{4,}" in the end of the expression).

LinkifyCompat.addLinks(textView, Linkify.ALL); // This will use the usual linkify for any other format
Pattern pattern = Pattern.compile("([\\d|\\(][\\h|\\(\\d{3}\\)|\\.|\\-|\\d]{4,}\\d{4,})", Pattern.CASE_INSENSITIVE);
    LinkifyCompat.addLinks(textView, pattern, "tel://", null, null, null); // this adds the format for all kinds of phone number

If you want to link just the numbers, remove the first line (the one with "Linkify.ALL").

Isan Campos
  • 321
  • 2
  • 5