1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.i18n;
18 
19 import android.support.annotation.Nullable;
20 import android.telephony.PhoneNumberUtils;
21 import android.text.SpannableStringBuilder;
22 import android.text.SpannedString;
23 import android.text.TextUtils;
24 import android.util.Patterns;
25 import java.util.regex.Matcher;
26 
27 /** A formatter that applies bidirectional formatting to phone numbers in text. */
28 public final class DialerBidiFormatter {
29 
30   /** Unicode "Left-To-Right Embedding" (LRE) character. */
31   private static final char LRE = '\u202A';
32 
33   /** Unicode "Pop Directional Formatting" (PDF) character. */
34   private static final char PDF = '\u202C';
35 
DialerBidiFormatter()36   private DialerBidiFormatter() {}
37 
38   /**
39    * Divides the given text into segments, applies LTR formatting and adds TTS span to segments that
40    * are phone numbers, then reassembles the text.
41    *
42    * <p>Formatted phone numbers usually contain one or more whitespaces (e.g., "+1 650-253-0000",
43    * "(650) 253-0000", etc). The system mistakes such a number for tokens separated by whitespaces.
44    * Therefore, these numbers can't be correctly shown in a RTL context (e.g., "+1 650-253-0000"
45    * would be shown as "650-253-0000 1+".)
46    *
47    * <p>This method wraps phone numbers with Unicode formatting characters LRE & PDF to ensure phone
48    * numbers are always shown as LTR strings.
49    *
50    * <p>Note that the regex used to find phone numbers ({@link Patterns#PHONE}) will also match any
51    * number. As this method also adds TTS span to segments that match {@link Patterns#PHONE}, extra
52    * actions need to be taken if you don't want a number to be read as a phone number by TalkBack.
53    */
format(@ullable CharSequence text)54   public static CharSequence format(@Nullable CharSequence text) {
55     if (TextUtils.isEmpty(text)) {
56       return text;
57     }
58 
59     SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
60 
61     // Find the start index and the end index of each segment matching the phone number pattern.
62     Matcher matcher = Patterns.PHONE.matcher(text.toString());
63 
64     int currIndex = 0;
65     while (matcher.find()) {
66       int start = matcher.start();
67       int end = matcher.end();
68 
69       // Handle the case where the input text doesn't start with a phone number.
70       if (currIndex < start) {
71         spannableStringBuilder.append(text.subSequence(currIndex, start));
72       }
73 
74       // For a phone number, wrap it with Unicode characters LRE & PDF so that it will always be
75       // shown as a LTR string.
76       spannableStringBuilder.append(
77           PhoneNumberUtils.createTtsSpannable(
78               TextUtils.concat(
79                   String.valueOf(LRE), text.subSequence(start, end), String.valueOf(PDF))));
80 
81       currIndex = end;
82     }
83 
84     // Handle the case where the input doesn't end with a phone number.
85     if (currIndex < text.length()) {
86       spannableStringBuilder.append(text.subSequence(currIndex, text.length()));
87     }
88 
89     return new SpannedString(spannableStringBuilder);
90   }
91 }
92