1 /*
2  * Copyright (C) 2012 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.contacts.common.util;
18 
19 import static android.provider.ContactsContract.CommonDataKinds.Phone;
20 
21 import android.content.Context;
22 import android.telephony.PhoneNumberUtils;
23 import android.text.Spannable;
24 import android.text.SpannableString;
25 import android.text.style.TtsSpan;
26 import android.util.Log;
27 import android.util.Patterns;
28 
29 import com.android.contacts.common.R;
30 
31 import com.android.i18n.phonenumbers.NumberParseException;
32 import com.android.i18n.phonenumbers.PhoneNumberUtil;
33 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
34 import com.google.common.base.Preconditions;
35 
36 /**
37  * Methods for handling various contact data labels.
38  */
39 public class ContactDisplayUtils {
40 
41     private static final String TAG = ContactDisplayUtils.class.getSimpleName();
42 
43     public static final int INTERACTION_CALL = 1;
44     public static final int INTERACTION_SMS = 2;
45 
46     /**
47      * Checks if the given data type is a custom type.
48      *
49      * @param type Phone data type.
50      * @return {@literal true} if the type is custom.  {@literal false} if not.
51      */
isCustomPhoneType(Integer type)52     public static boolean isCustomPhoneType(Integer type) {
53         return type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT;
54     }
55 
56     /**
57      * Gets a display label for a given phone type.
58      *
59      * @param type The type of number.
60      * @param customLabel A custom label to use if the phone is determined to be of custom type
61      * determined by {@link #isCustomPhoneType(Integer))}
62      * @param interactionType whether this is a call or sms.  Either {@link #INTERACTION_CALL} or
63      * {@link #INTERACTION_SMS}.
64      * @param context The application context.
65      * @return An appropriate string label
66      */
getLabelForCallOrSms(Integer type, CharSequence customLabel, int interactionType, Context context)67     public static CharSequence getLabelForCallOrSms(Integer type, CharSequence customLabel,
68             int interactionType, Context context) {
69         Preconditions.checkNotNull(context);
70 
71         if (isCustomPhoneType(type)) {
72             return (customLabel == null) ? "" : customLabel;
73         } else {
74             int resId;
75             if (interactionType == INTERACTION_SMS) {
76                 resId = getSmsLabelResourceId(type);
77             } else {
78                 resId = getPhoneLabelResourceId(type);
79                 if (interactionType != INTERACTION_CALL) {
80                     Log.e(TAG, "Un-recognized interaction type: " + interactionType +
81                             ". Defaulting to ContactDisplayUtils.INTERACTION_CALL.");
82                 }
83             }
84 
85             return context.getResources().getText(resId);
86         }
87     }
88 
89     /**
90      * Find a label for calling.
91      *
92      * @param type The type of number.
93      * @return An appropriate string label.
94      */
getPhoneLabelResourceId(Integer type)95     public static int getPhoneLabelResourceId(Integer type) {
96         if (type == null) return R.string.call_other;
97         switch (type) {
98             case Phone.TYPE_HOME:
99                 return R.string.call_home;
100             case Phone.TYPE_MOBILE:
101                 return R.string.call_mobile;
102             case Phone.TYPE_WORK:
103                 return R.string.call_work;
104             case Phone.TYPE_FAX_WORK:
105                 return R.string.call_fax_work;
106             case Phone.TYPE_FAX_HOME:
107                 return R.string.call_fax_home;
108             case Phone.TYPE_PAGER:
109                 return R.string.call_pager;
110             case Phone.TYPE_OTHER:
111                 return R.string.call_other;
112             case Phone.TYPE_CALLBACK:
113                 return R.string.call_callback;
114             case Phone.TYPE_CAR:
115                 return R.string.call_car;
116             case Phone.TYPE_COMPANY_MAIN:
117                 return R.string.call_company_main;
118             case Phone.TYPE_ISDN:
119                 return R.string.call_isdn;
120             case Phone.TYPE_MAIN:
121                 return R.string.call_main;
122             case Phone.TYPE_OTHER_FAX:
123                 return R.string.call_other_fax;
124             case Phone.TYPE_RADIO:
125                 return R.string.call_radio;
126             case Phone.TYPE_TELEX:
127                 return R.string.call_telex;
128             case Phone.TYPE_TTY_TDD:
129                 return R.string.call_tty_tdd;
130             case Phone.TYPE_WORK_MOBILE:
131                 return R.string.call_work_mobile;
132             case Phone.TYPE_WORK_PAGER:
133                 return R.string.call_work_pager;
134             case Phone.TYPE_ASSISTANT:
135                 return R.string.call_assistant;
136             case Phone.TYPE_MMS:
137                 return R.string.call_mms;
138             default:
139                 return R.string.call_custom;
140         }
141 
142     }
143 
144     /**
145      * Find a label for sending an sms.
146      *
147      * @param type The type of number.
148      * @return An appropriate string label.
149      */
getSmsLabelResourceId(Integer type)150     public static int getSmsLabelResourceId(Integer type) {
151         if (type == null) return R.string.sms_other;
152         switch (type) {
153             case Phone.TYPE_HOME:
154                 return R.string.sms_home;
155             case Phone.TYPE_MOBILE:
156                 return R.string.sms_mobile;
157             case Phone.TYPE_WORK:
158                 return R.string.sms_work;
159             case Phone.TYPE_FAX_WORK:
160                 return R.string.sms_fax_work;
161             case Phone.TYPE_FAX_HOME:
162                 return R.string.sms_fax_home;
163             case Phone.TYPE_PAGER:
164                 return R.string.sms_pager;
165             case Phone.TYPE_OTHER:
166                 return R.string.sms_other;
167             case Phone.TYPE_CALLBACK:
168                 return R.string.sms_callback;
169             case Phone.TYPE_CAR:
170                 return R.string.sms_car;
171             case Phone.TYPE_COMPANY_MAIN:
172                 return R.string.sms_company_main;
173             case Phone.TYPE_ISDN:
174                 return R.string.sms_isdn;
175             case Phone.TYPE_MAIN:
176                 return R.string.sms_main;
177             case Phone.TYPE_OTHER_FAX:
178                 return R.string.sms_other_fax;
179             case Phone.TYPE_RADIO:
180                 return R.string.sms_radio;
181             case Phone.TYPE_TELEX:
182                 return R.string.sms_telex;
183             case Phone.TYPE_TTY_TDD:
184                 return R.string.sms_tty_tdd;
185             case Phone.TYPE_WORK_MOBILE:
186                 return R.string.sms_work_mobile;
187             case Phone.TYPE_WORK_PAGER:
188                 return R.string.sms_work_pager;
189             case Phone.TYPE_ASSISTANT:
190                 return R.string.sms_assistant;
191             case Phone.TYPE_MMS:
192                 return R.string.sms_mms;
193             default:
194                 return R.string.sms_custom;
195         }
196     }
197 
198     /**
199      * Whether the given text could be a phone number.
200      *
201      * Note this will miss many things that are legitimate phone numbers, for example,
202      * phone numbers with letters.
203      */
isPossiblePhoneNumber(CharSequence text)204     public static boolean isPossiblePhoneNumber(CharSequence text) {
205         return text == null ? false : Patterns.PHONE.matcher(text.toString()).matches();
206     }
207 
208     /**
209      * Returns a Spannable for the given phone number with a telephone {@link TtsSpan} set over
210      * the entire length of the given phone number.
211      */
getTelephoneTtsSpannable(String phoneNumber)212     public static Spannable getTelephoneTtsSpannable(String phoneNumber) {
213         if (phoneNumber == null) {
214             return null;
215         }
216         final Spannable spannable = new SpannableString(phoneNumber);
217         final TtsSpan ttsSpan = getTelephoneTtsSpan(phoneNumber);
218         spannable.setSpan(ttsSpan, 0, phoneNumber.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
219         return spannable;
220     }
221 
222     /**
223      * Returns a Spannable for the given message with a telephone {@link TtsSpan} set for
224      * the given phone number text wherever it is found within the message.
225      */
getTelephoneTtsSpannable(String message, String phoneNumber)226     public static Spannable getTelephoneTtsSpannable(String message, String phoneNumber) {
227         if (message == null) {
228             return null;
229         }
230         final Spannable spannable = new SpannableString(message);
231         int start = phoneNumber == null ? -1 : message.indexOf(phoneNumber);
232         while (start >= 0) {
233             final int end = start + phoneNumber.length();
234             final TtsSpan ttsSpan = getTelephoneTtsSpan(phoneNumber);
235             spannable.setSpan(ttsSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
236             start = message.indexOf(phoneNumber, end);
237         }
238         return spannable;
239     }
240 
241     /**
242      * Returns a telephone {@link TtsSpan} for the given phone number.
243      */
getTelephoneTtsSpan(String phoneNumberString)244     public static TtsSpan getTelephoneTtsSpan(String phoneNumberString) {
245         if (phoneNumberString == null) {
246             throw new NullPointerException();
247         }
248 
249         // Parse the phone number
250         final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
251         PhoneNumber phoneNumber = null;
252         try {
253             // Don't supply a defaultRegion so this fails for non-international numbers because
254             // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already
255             // present
256             phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null);
257         } catch (NumberParseException ignored) {
258         }
259 
260         // Build a telephone tts span
261         final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder();
262         if (phoneNumber == null) {
263             // Strip separators otherwise TalkBack will be silent
264             // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel)
265             builder.setNumberParts(PhoneNumberUtils.stripSeparators(phoneNumberString));
266         } else {
267             if (phoneNumber.hasCountryCode()) {
268                 builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode()));
269             }
270             builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber()));
271         }
272         return builder.build();
273     }
274 }
275