25

I have a simple TextView with local phone number 852112222 or (8 5) 211 2222.

I need it to be clickable, so naturally I used android:autoLink="all".
But for some reason I don't understand same phone number is not "linkified" on all devices.

On plain Genymotion device it didn't work. On my personal OnePlus2 device it worked. Tested on bunch on different devices - no luck.

What could be the issue?
User account preferences? Android version? ORM? Something else?

Martynas Jurkus
  • 9,231
  • 13
  • 59
  • 101
  • my observation is "+" prefix is required before a number to open dailer via xml directly just like "tel:" prefix is required for setting call intent programmatically. – Muahmmad Tayyib May 27 '19 at 09:29

12 Answers12

26

Here is my investigation.

I created a new project, and added android:autoLink="all" to a text view in activity_main.xml. Thanks to the developers of Android Studio, I could see the preview, and I found something interesting:

  • 12345 not linked
  • 123456 not linked
  • 1234567 linked
  • 12345678 linked
  • 123456789 not linked
  • 1234567890 not likned
  • 12345678901 linked
  • 123456789012 not linked

The result is the same on my phone. So I looked into the source code, searched for the keyword autolink, then I found this:

private void setText(CharSequence text, BufferType type,
                     boolean notifyBefore, int oldlen) {

    ...
    // unconcerned code above

    if (mAutoLinkMask != 0) {
        Spannable s2;

        if (type == BufferType.EDITABLE || text instanceof Spannable) {
            s2 = (Spannable) text;
        } else {
            s2 = mSpannableFactory.newSpannable(text);
        }

        if (Linkify.addLinks(s2, mAutoLinkMask)) {
            text = s2;
            type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;

            /*
             * We must go ahead and set the text before changing the
             * movement method, because setMovementMethod() may call
             * setText() again to try to upgrade the buffer type.
             */
            mText = text;

            // Do not change the movement method for text that support text selection as it
            // would prevent an arbitrary cursor displacement.
            if (mLinksClickable && !textCanBeSelected()) {
                setMovementMethod(LinkMovementMethod.getInstance());
            }
        }
    }

    ...
    // unconcerned code above
}

So the keyword is Linkify now. For addLinks:

public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) {
    ...

    if ((mask & PHONE_NUMBERS) != 0) {
        gatherTelLinks(links, text);
    }

    ...
}

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

Then, something bad happened, the SDK doesn't have PhoneNumberUtil, specifically these 3 classes below:

import com.android.i18n.phonenumbers.PhoneNumberMatch;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;

For now, the first reason surfaced: Locale.getDefault().getCountry().
So I went to setting, found language, selected Chinese. The result is below:

  • 12345 linked
  • 123456 linked
  • 1234567 linked
  • 12345678 linked
  • 123456789 linked
  • 1234567890 linked
  • 12345678901 linked
  • 123456789012 linked

Secondly, for the package of com.android.i18n.phonenumbers, I found this:
https://android.googlesource.com/platform/external/libphonenumber/+/ics-factoryrom-2-release/java/src/com/android/i18n/phonenumbers
If you are interested, check the link above. Notice in the URL: ics-factoryrom-2-release. So I highly doubt that this is platform-dependent.

For the solution, CleverAndroid is right, taking full control of LinkMovementMethod is a good option.

Pang
  • 9,564
  • 146
  • 81
  • 122
Chris Wong
  • 451
  • 3
  • 5
19

Just do the following

TextView userInput= (TextView) view.findViewById(R.id.textView);

if(userInput != null){
     Linkify.addLinks(userInput, Patterns.PHONE,"tel:",Linkify.sPhoneNumberMatchFilter,Linkify.sPhoneNumberTransformFilter);
     userInput.setMovementMethod(LinkMovementMethod.getInstance());
}

and also remove the

android:autoLink

from your xml file

Harsh Agrawal
  • 597
  • 3
  • 12
  • 1
    +1 this works perfectly for me on (almost) all devices. The only one it doesn't seem to work on was an 3.2 QVGA emulator (Android 7.1.1). If anyone has a clue why, I'd be happy to hear about it. Still, I'm happy with the big improvement over autolink, thanks! ... ps. it did work as expected on a Nexus One - Android 6.0 - emulator. So it's not an v7.1.1 and lower issue, although it might be due to a single specific Android version. I haven't continued to test others, because for me the result works sufficiently nonetheless. – P Kuijpers Jul 29 '21 at 06:50
13

I made a universal pattern for phone numbers and added a Linkify mask. Kotlin, extension function:

fun TextView.makeLinkedable(){
    val pattern = Pattern.compile("""([\d|\(][\h|\(\d{3}\)|\.|\-|\d]{4,}\d)""",
    Pattern.CASE_INSENSITIVE)
    LinkifyCompat.addLinks(this, Linkify.ALL)
    LinkifyCompat.addLinks(this, pattern, "tel://", null, null, null)
    setLinkTextColor(ContextCompat.getColor(context, R.color.blue))
}

Should work for all devices

The point is in this:

val pattern = Pattern.compile("""([\d|\(][\h|\(\d{3}\)|\.|\-|\d]{4,}\d)""",
Pattern.CASE_INSENSITIVE)
LinkifyCompat.addLinks(this, pattern, "tel://", null, null, null)

And in Java:

 Pattern pattern = Pattern.compile("([\\d|(][\\h|(\\d{3})|.|\\-|\\d]{4,}\\d{4,})", Pattern.CASE_INSENSITIVE);
 LinkifyCompat.addLinks(textView, pattern, "tel://", null, null, null);
Ilya Mashin
  • 904
  • 1
  • 12
  • 28
  • This crashes on Lollipop for me, I think due to `\h` being a Java 8 addition. This works for me instead `"""([\d|\(][\t\p{Zs}|\(\d{3}\)|\.|\-|\d]{4,}\d)"""` – aaronmarino Oct 14 '19 at 16:05
8

I would suggest you just to add country code and all your issue will be resolved,

android:autoLink="phone"
android:text="+91-8000000000"

If we add country code before the number, then no need of other temporary solutions.

miPlodder
  • 795
  • 8
  • 18
  • That would work. Although phone numbers is user input and it is not common practice to enter country codes when entering same country phone number so I can't force validation on them. – Martynas Jurkus Feb 26 '18 at 07:57
  • Adding Country Code will help developer to resolve, future issues when his/her Android App is used globally. In that case, user will not be able to make a call due to no country code (Invalid number). So, why not add it beforehand to resolve future issues, and this approach will even help developer from adding other temporary solutions to make `android:autoLink` work. – miPlodder Feb 27 '18 at 07:03
  • @miPlodder The problem is that this is a user facing string, and we may not want to display phone numbers to the user like that. For US numbers, you would probably not want to show numbers as +12345678900, as that would be hard for the user to read and doesn't look nice. You would probably want it to be (234) 567-8900. – mithunc Feb 24 '22 at 17:30
  • this is the simple and right solution – Noor Hossain Aug 18 '22 at 08:30
6

Harsh Agrawal answer worked for me in cases where the phone number is 11 digits or more with 3 blocks. e.g. 123 456 78910

TextView textView = findViewById(R.id.text_view);
textView.setText("123 456 78910");
Linkify.addLinks(textView, Patterns.PHONE, "tel:", Linkify.sPhoneNumberMatchFilter,
            Linkify.sPhoneNumberTransformFilter);

I had to call Linkify.addLinks after setting text for it to work.

Note that Linkify.addLinks already calls setMovementMethod on the text view.

fluxeon
  • 61
  • 1
  • 2
5

Can you please try below code. Set attribute programmatically.

Activity

package custom.com.android_lab;

import android.app.Activity;
import android.os.Bundle;
import android.text.util.Linkify;
import android.widget.TextView;

/**
 * You can use Activity or AppCompatActivity
 */
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // local phone number 852112222 or (8 5) 211 2222.

        // Tested OK!
        TextView textView1 = (TextView) findViewById(R.id.textv1);
        textView1.setAutoLinkMask(Linkify.PHONE_NUMBERS);
        textView1.setText("852112222");

        // Tested OK!
        TextView textView2 = (TextView) findViewById(R.id.textv2);
        textView2.setAutoLinkMask(Linkify.PHONE_NUMBERS);
        textView2.setText("(85) 211-2222");

        // Tested Failed!
        // Reason : Need to apply setAutoLinkMask prior to apply setText
        TextView textView3 = (TextView) findViewById(R.id.textv3);
        textView3.setText("852112222");
        textView2.setAutoLinkMask(Linkify.PHONE_NUMBERS);

    }
}   

View

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textv1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textv2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textv3"
        android:layout_width="match_parent"
        android:layout_height="55dp" />

</LinearLayout>   

enter image description here

Testing Devices

  1. One plus one with Android 7.1
  2. Genymotion. 4.1.1 - API 16
Mable John
  • 4,518
  • 3
  • 22
  • 35
5

I have seen a lot of inconsistencies when using auto link. If at all you are working for a prod version. Always go for SpannableStringBuilder with LinkMovementMethod. You have good control of how the text needs to be displayed.

The snippet below may help.

String phone = "your phone number";
String message = "Phone number is: ";

Spannable span = new SpannableString(String.format("%s\n%s",message, phone));
ForegroundColorSpan color = new ForegroundColorSpan(Res.color(R.color.blue));
ClickableSpan click = new ClickableSpan() {
    @Override
        public void onClick(View widget) {
            Navigator.dialer("your phone number");
        }
        public void updateDrawState(TextPaint ds) {// override updateDrawState
            ds.setUnderlineText(false); // set to false to remove underline
        }
    };

span.setSpan(color, message.length(), message.length() + phone.length() + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
span.setSpan(click, message.length(), message.length() + phone.length() + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);

changesMessageLabel.setText(span);
changesMessageLabel.setMovementMethod(LinkMovementMethod.getInstance());
2

Iliya Mashin's answer was the best solution for me.

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 "{4,}" in the ending).

    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").

gmauch
  • 1,316
  • 4
  • 25
  • 39
Isan Campos
  • 321
  • 2
  • 5
2

I created an extension function for the textview to make phonenumbers work on all devices:

import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.util.Patterns
import android.widget.TextView
import androidx.core.text.util.LinkifyCompat

fun TextView.compatMakeLinksClickable() {
    LinkifyCompat.addLinks(this, Linkify.ALL)
    // For some phone manufacturers (e.g. Moto G5 plus and some samsung devices)
    // Linkify.ALL does not work for phonenumbers
    // therefore we added the next line, to fix that:
    Linkify.addLinks(
        this, Patterns.PHONE, "tel:",
        Linkify.sPhoneNumberMatchFilter,
        Linkify.sPhoneNumberTransformFilter
    );
    this.movementMethod = LinkMovementMethod.getInstance()
}
Hans
  • 1,886
  • 24
  • 18
  • The second `Linkify.addLinks` can use `LinkifyCompat` as well. Also, I didn't need to set `movementMethod`, I think `addLinks` does that on its own. – mithunc Mar 02 '22 at 01:09
0

Some numbers are not accepted by the autoLink="phone"

So you can call directly the Phone Intent adding a clickListener to the TextView.

Declare phone as a class attribute:

private String phone = "1234567890";

At the onCreate() method of the activity:

//...
TextView  tvPhoneTelefone = (TextView) findViewById(R.id.tv_telefone);
tvPhone.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent callIntent = new Intent(Intent.ACTION_DIAL);
        callIntent.setData(Uri.parse("tel:" + phone ));
        startActivity(callIntent);
    }
});

This should work with any number.

Derzu
  • 7,011
  • 3
  • 57
  • 60
0

I made phone number clickable using linkify and it is worked for me

private val PHONE_NUMBER_VALIDATE = "^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\\s./0-9]*\$"

    val spannableString = SpannableString(mMarker.snippet)
    val pattern = Pattern.compile(PHONE_NUMBER_VALIDATE, Pattern.MULTILINE)
    LinkifyCompat.addLinks(spannableString, pattern, "tel://")
    textview.text = spannableString
    textview.movementMethod = LinkMovementMethod.getInstance()

make sure you have not added android:autoLink="phone" in XML

Here, I have tried Pattern.MULTILINE to compile the pattern

Pattern.MULTILINE tells Java to accept the anchors ^ and $ to match at the start and end of each line (otherwise they only match at the start/end of the entire string).

Mittal Varsani
  • 5,601
  • 2
  • 15
  • 27
-1

For phone autolink specific you should use

android:autoLink="phone"

For more refer: textview autolink

Ready Android
  • 3,529
  • 2
  • 26
  • 40