/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.dialer.i18n; import android.support.annotation.Nullable; import android.telephony.PhoneNumberUtils; import android.text.SpannableStringBuilder; import android.text.SpannedString; import android.text.TextUtils; import android.util.Patterns; import java.util.regex.Matcher; /** A formatter that applies bidirectional formatting to phone numbers in text. */ public final class DialerBidiFormatter { /** Unicode "Left-To-Right Embedding" (LRE) character. */ private static final char LRE = '\u202A'; /** Unicode "Pop Directional Formatting" (PDF) character. */ private static final char PDF = '\u202C'; private DialerBidiFormatter() {} /** * Divides the given text into segments, applies LTR formatting and adds TTS span to segments that * are phone numbers, then reassembles the text. * *

Formatted phone numbers usually contain one or more whitespaces (e.g., "+1 650-253-0000", * "(650) 253-0000", etc). The system mistakes such a number for tokens separated by whitespaces. * Therefore, these numbers can't be correctly shown in a RTL context (e.g., "+1 650-253-0000" * would be shown as "650-253-0000 1+".) * *

This method wraps phone numbers with Unicode formatting characters LRE & PDF to ensure phone * numbers are always shown as LTR strings. * *

Note that the regex used to find phone numbers ({@link Patterns#PHONE}) will also match any * number. As this method also adds TTS span to segments that match {@link Patterns#PHONE}, extra * actions need to be taken if you don't want a number to be read as a phone number by TalkBack. */ public static CharSequence format(@Nullable CharSequence text) { if (TextUtils.isEmpty(text)) { return text; } SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); // Find the start index and the end index of each segment matching the phone number pattern. Matcher matcher = Patterns.PHONE.matcher(text.toString()); int currIndex = 0; while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); // Handle the case where the input text doesn't start with a phone number. if (currIndex < start) { spannableStringBuilder.append(text.subSequence(currIndex, start)); } // For a phone number, wrap it with Unicode characters LRE & PDF so that it will always be // shown as a LTR string. spannableStringBuilder.append( PhoneNumberUtils.createTtsSpannable( TextUtils.concat( String.valueOf(LRE), text.subSequence(start, end), String.valueOf(PDF)))); currIndex = end; } // Handle the case where the input doesn't end with a phone number. if (currIndex < text.length()) { spannableStringBuilder.append(text.subSequence(currIndex, text.length())); } return new SpannedString(spannableStringBuilder); } }