• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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;
18 
19 import com.android.contacts.activities.DialtactsActivity;
20 import com.android.contacts.model.AccountType;
21 import com.android.contacts.model.AccountTypeManager;
22 import com.android.contacts.model.AccountWithDataSet;
23 import com.android.contacts.test.NeededForTesting;
24 import com.android.contacts.util.Constants;
25 
26 import android.content.Context;
27 import android.content.Intent;
28 import android.database.Cursor;
29 import android.graphics.Rect;
30 import android.location.CountryDetector;
31 import android.net.Uri;
32 import android.provider.ContactsContract;
33 import android.provider.ContactsContract.CommonDataKinds.Im;
34 import android.provider.ContactsContract.CommonDataKinds.Phone;
35 import android.provider.ContactsContract.DisplayPhoto;
36 import android.provider.ContactsContract.QuickContact;
37 import android.telephony.PhoneNumberUtils;
38 import android.text.TextUtils;
39 import android.view.View;
40 import android.widget.TextView;
41 
42 import java.util.List;
43 
44 public class ContactsUtils {
45     private static final String TAG = "ContactsUtils";
46     private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);
47 
48     private static int sThumbnailSize = -1;
49 
50     // TODO find a proper place for the canonical version of these
51     public interface ProviderNames {
52         String YAHOO = "Yahoo";
53         String GTALK = "GTalk";
54         String MSN = "MSN";
55         String ICQ = "ICQ";
56         String AIM = "AIM";
57         String XMPP = "XMPP";
58         String JABBER = "JABBER";
59         String SKYPE = "SKYPE";
60         String QQ = "QQ";
61     }
62 
63     /**
64      * This looks up the provider name defined in
65      * ProviderNames from the predefined IM protocol id.
66      * This is used for interacting with the IM application.
67      *
68      * @param protocol the protocol ID
69      * @return the provider name the IM app uses for the given protocol, or null if no
70      * provider is defined for the given protocol
71      * @hide
72      */
lookupProviderNameFromId(int protocol)73     public static String lookupProviderNameFromId(int protocol) {
74         switch (protocol) {
75             case Im.PROTOCOL_GOOGLE_TALK:
76                 return ProviderNames.GTALK;
77             case Im.PROTOCOL_AIM:
78                 return ProviderNames.AIM;
79             case Im.PROTOCOL_MSN:
80                 return ProviderNames.MSN;
81             case Im.PROTOCOL_YAHOO:
82                 return ProviderNames.YAHOO;
83             case Im.PROTOCOL_ICQ:
84                 return ProviderNames.ICQ;
85             case Im.PROTOCOL_JABBER:
86                 return ProviderNames.JABBER;
87             case Im.PROTOCOL_SKYPE:
88                 return ProviderNames.SKYPE;
89             case Im.PROTOCOL_QQ:
90                 return ProviderNames.QQ;
91         }
92         return null;
93     }
94 
95     /**
96      * Test if the given {@link CharSequence} contains any graphic characters,
97      * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
98      */
isGraphic(CharSequence str)99     public static boolean isGraphic(CharSequence str) {
100         return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
101     }
102 
103     /**
104      * Returns true if two objects are considered equal.  Two null references are equal here.
105      */
106     @NeededForTesting
areObjectsEqual(Object a, Object b)107     public static boolean areObjectsEqual(Object a, Object b) {
108         return a == b || (a != null && a.equals(b));
109     }
110 
111     /**
112      * Returns true if two data with mimetypes which represent values in contact entries are
113      * considered equal for collapsing in the GUI. For caller-id, use
114      * {@link PhoneNumberUtils#compare(Context, String, String)} instead
115      */
shouldCollapse(CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2)116     public static final boolean shouldCollapse(CharSequence mimetype1, CharSequence data1,
117             CharSequence mimetype2, CharSequence data2) {
118         // different mimetypes? don't collapse
119         if (!TextUtils.equals(mimetype1, mimetype2)) return false;
120 
121         // exact same string? good, bail out early
122         if (TextUtils.equals(data1, data2)) return true;
123 
124         // so if either is null, these two must be different
125         if (data1 == null || data2 == null) return false;
126 
127         // if this is not about phone numbers, we know this is not a match (of course, some
128         // mimetypes could have more sophisticated matching is the future, e.g. addresses)
129         if (!TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype1)) return false;
130 
131         return shouldCollapsePhoneNumbers(data1.toString(), data2.toString());
132     }
133 
shouldCollapsePhoneNumbers( String number1WithLetters, String number2WithLetters)134     private static final boolean shouldCollapsePhoneNumbers(
135             String number1WithLetters, String number2WithLetters) {
136         final String number1 = PhoneNumberUtils.convertKeypadLettersToDigits(number1WithLetters);
137         final String number2 = PhoneNumberUtils.convertKeypadLettersToDigits(number2WithLetters);
138 
139         int index1 = 0;
140         int index2 = 0;
141         for (;;) {
142             // Skip formatting characters.
143             while (index1 < number1.length() &&
144                     !PhoneNumberUtils.isNonSeparator(number1.charAt(index1))) {
145                 index1++;
146             }
147             while (index2 < number2.length() &&
148                     !PhoneNumberUtils.isNonSeparator(number2.charAt(index2))) {
149                 index2++;
150             }
151             // If both have finished, match.  If only one has finished, not match.
152             final boolean number1End = (index1 == number1.length());
153             final boolean number2End = (index2 == number2.length());
154             if (number1End) {
155                 return number2End;
156             }
157             if (number2End) return false;
158 
159             // If the non-formatting characters are different, not match.
160             if (number1.charAt(index1) != number2.charAt(index2)) return false;
161 
162             // Go to the next characters.
163             index1++;
164             index2++;
165         }
166     }
167 
168     /**
169      * Returns true if two {@link Intent}s are both null, or have the same action.
170      */
areIntentActionEqual(Intent a, Intent b)171     public static final boolean areIntentActionEqual(Intent a, Intent b) {
172         if (a == b) {
173             return true;
174         }
175         if (a == null || b == null) {
176             return false;
177         }
178         return TextUtils.equals(a.getAction(), b.getAction());
179     }
180 
181     /**
182      * @return The ISO 3166-1 two letters country code of the country the user
183      *         is in.
184      */
getCurrentCountryIso(Context context)185     public static final String getCurrentCountryIso(Context context) {
186         CountryDetector detector =
187                 (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR);
188         return detector.detectCountry().getCountryIso();
189     }
190 
areContactWritableAccountsAvailable(Context context)191     public static boolean areContactWritableAccountsAvailable(Context context) {
192         final List<AccountWithDataSet> accounts =
193                 AccountTypeManager.getInstance(context).getAccounts(true /* writeable */);
194         return !accounts.isEmpty();
195     }
196 
areGroupWritableAccountsAvailable(Context context)197     public static boolean areGroupWritableAccountsAvailable(Context context) {
198         final List<AccountWithDataSet> accounts =
199                 AccountTypeManager.getInstance(context).getGroupWritableAccounts();
200         return !accounts.isEmpty();
201     }
202 
203     /**
204      * Returns the intent to launch for the given invitable account type and contact lookup URI.
205      * This will return null if the account type is not invitable (i.e. there is no
206      * {@link AccountType#getInviteContactActivityClassName()} or
207      * {@link AccountType#syncAdapterPackageName}).
208      */
getInvitableIntent(AccountType accountType, Uri lookupUri)209     public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) {
210         String syncAdapterPackageName = accountType.syncAdapterPackageName;
211         String className = accountType.getInviteContactActivityClassName();
212         if (TextUtils.isEmpty(syncAdapterPackageName) || TextUtils.isEmpty(className)) {
213             return null;
214         }
215         Intent intent = new Intent();
216         intent.setClassName(syncAdapterPackageName, className);
217 
218         intent.setAction(ContactsContract.Intents.INVITE_CONTACT);
219 
220         // Data is the lookup URI.
221         intent.setData(lookupUri);
222         return intent;
223     }
224 
225     /**
226      * Return Uri with an appropriate scheme, accepting Voicemail, SIP, and usual phone call
227      * numbers.
228      */
getCallUri(String number)229     public static Uri getCallUri(String number) {
230         if (PhoneNumberUtils.isVoiceMailNumber(number)) {
231             return Uri.parse("voicemail:");
232         }
233         if (PhoneNumberUtils.isUriNumber(number)) {
234              return Uri.fromParts(Constants.SCHEME_SIP, number, null);
235         }
236         return Uri.fromParts(Constants.SCHEME_TEL, number, null);
237      }
238 
239     /**
240      * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined
241      * automatically.
242      */
getCallIntent(String number)243     public static Intent getCallIntent(String number) {
244         return getCallIntent(number, null);
245     }
246 
247     /**
248      * Return an Intent for making a phone call. A given Uri will be used as is (without any
249      * sanity check).
250      */
getCallIntent(Uri uri)251     public static Intent getCallIntent(Uri uri) {
252         return getCallIntent(uri, null);
253     }
254 
255     /**
256      * A variant of {@link #getCallIntent(String)} but also accept a call origin. For more
257      * information about call origin, see comments in Phone package (PhoneApp).
258      */
getCallIntent(String number, String callOrigin)259     public static Intent getCallIntent(String number, String callOrigin) {
260         return getCallIntent(getCallUri(number), callOrigin);
261     }
262 
263     /**
264      * A variant of {@link #getCallIntent(Uri)} but also accept a call origin. For more
265      * information about call origin, see comments in Phone package (PhoneApp).
266      */
getCallIntent(Uri uri, String callOrigin)267     public static Intent getCallIntent(Uri uri, String callOrigin) {
268         final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri);
269         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
270         if (callOrigin != null) {
271             intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN, callOrigin);
272         }
273         return intent;
274     }
275 
276     /**
277      * Return an Intent for launching voicemail screen.
278      */
getVoicemailIntent()279     public static Intent getVoicemailIntent() {
280         final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
281                 Uri.fromParts("voicemail", "", null));
282         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
283         return intent;
284     }
285 
286     /**
287      * Returns a header view based on the R.layout.list_separator, where the
288      * containing {@link TextView} is set using the given textResourceId.
289      */
createHeaderView(Context context, int textResourceId)290     public static View createHeaderView(Context context, int textResourceId) {
291         View view = View.inflate(context, R.layout.list_separator, null);
292         TextView textView = (TextView) view.findViewById(R.id.title);
293         textView.setText(context.getString(textResourceId));
294         return view;
295     }
296 
297     /**
298      * Returns the {@link Rect} with left, top, right, and bottom coordinates
299      * that are equivalent to the given {@link View}'s bounds. This is equivalent to how the
300      * target {@link Rect} is calculated in {@link QuickContact#showQuickContact}.
301      */
getTargetRectFromView(Context context, View view)302     public static Rect getTargetRectFromView(Context context, View view) {
303         final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
304         final int[] pos = new int[2];
305         view.getLocationOnScreen(pos);
306 
307         final Rect rect = new Rect();
308         rect.left = (int) (pos[0] * appScale + 0.5f);
309         rect.top = (int) (pos[1] * appScale + 0.5f);
310         rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f);
311         rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f);
312         return rect;
313     }
314 
315     /**
316      * Returns the size (width and height) of thumbnail pictures as configured in the provider. This
317      * can safely be called from the UI thread, as the provider can serve this without performing
318      * a database access
319      */
getThumbnailSize(Context context)320     public static int getThumbnailSize(Context context) {
321         if (sThumbnailSize == -1) {
322             final Cursor c = context.getContentResolver().query(
323                     DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
324                     new String[] { DisplayPhoto.THUMBNAIL_MAX_DIM }, null, null, null);
325             try {
326                 c.moveToFirst();
327                 sThumbnailSize = c.getInt(0);
328             } finally {
329                 c.close();
330             }
331         }
332         return sThumbnailSize;
333     }
334 }
335