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