Home | History | Annotate | Download | only in contacts
      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.model.AccountType;
     20 import com.android.contacts.model.AccountTypeManager;
     21 import com.android.contacts.model.AccountWithDataSet;
     22 import com.android.contacts.test.NeededForTesting;
     23 import com.android.i18n.phonenumbers.PhoneNumberUtil;
     24 
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.graphics.Rect;
     28 import android.location.CountryDetector;
     29 import android.net.Uri;
     30 import android.provider.ContactsContract;
     31 import android.provider.ContactsContract.CommonDataKinds.Im;
     32 import android.provider.ContactsContract.CommonDataKinds.Phone;
     33 import android.telephony.PhoneNumberUtils;
     34 import android.text.TextUtils;
     35 import android.view.View;
     36 import android.widget.TextView;
     37 
     38 import java.util.List;
     39 
     40 public class ContactsUtils {
     41     private static final String TAG = "ContactsUtils";
     42     private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);
     43 
     44 
     45     // TODO find a proper place for the canonical version of these
     46     public interface ProviderNames {
     47         String YAHOO = "Yahoo";
     48         String GTALK = "GTalk";
     49         String MSN = "MSN";
     50         String ICQ = "ICQ";
     51         String AIM = "AIM";
     52         String XMPP = "XMPP";
     53         String JABBER = "JABBER";
     54         String SKYPE = "SKYPE";
     55         String QQ = "QQ";
     56     }
     57 
     58     /**
     59      * This looks up the provider name defined in
     60      * ProviderNames from the predefined IM protocol id.
     61      * This is used for interacting with the IM application.
     62      *
     63      * @param protocol the protocol ID
     64      * @return the provider name the IM app uses for the given protocol, or null if no
     65      * provider is defined for the given protocol
     66      * @hide
     67      */
     68     public static String lookupProviderNameFromId(int protocol) {
     69         switch (protocol) {
     70             case Im.PROTOCOL_GOOGLE_TALK:
     71                 return ProviderNames.GTALK;
     72             case Im.PROTOCOL_AIM:
     73                 return ProviderNames.AIM;
     74             case Im.PROTOCOL_MSN:
     75                 return ProviderNames.MSN;
     76             case Im.PROTOCOL_YAHOO:
     77                 return ProviderNames.YAHOO;
     78             case Im.PROTOCOL_ICQ:
     79                 return ProviderNames.ICQ;
     80             case Im.PROTOCOL_JABBER:
     81                 return ProviderNames.JABBER;
     82             case Im.PROTOCOL_SKYPE:
     83                 return ProviderNames.SKYPE;
     84             case Im.PROTOCOL_QQ:
     85                 return ProviderNames.QQ;
     86         }
     87         return null;
     88     }
     89 
     90     /**
     91      * Test if the given {@link CharSequence} contains any graphic characters,
     92      * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
     93      */
     94     public static boolean isGraphic(CharSequence str) {
     95         return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
     96     }
     97 
     98     /**
     99      * Returns true if two objects are considered equal.  Two null references are equal here.
    100      */
    101     @NeededForTesting
    102     public static boolean areObjectsEqual(Object a, Object b) {
    103         return a == b || (a != null && a.equals(b));
    104     }
    105 
    106     /**
    107      * Returns true if two data with mimetypes which represent values in contact entries are
    108      * considered equal for collapsing in the GUI. For caller-id, use
    109      * {@link PhoneNumberUtils#compare(Context, String, String)} instead
    110      */
    111     public static final boolean shouldCollapse(CharSequence mimetype1, CharSequence data1,
    112             CharSequence mimetype2, CharSequence data2) {
    113         // different mimetypes? don't collapse
    114         if (!TextUtils.equals(mimetype1, mimetype2)) return false;
    115 
    116         // exact same string? good, bail out early
    117         if (TextUtils.equals(data1, data2)) return true;
    118 
    119         // so if either is null, these two must be different
    120         if (data1 == null || data2 == null) return false;
    121 
    122         // if this is not about phone numbers, we know this is not a match (of course, some
    123         // mimetypes could have more sophisticated matching is the future, e.g. addresses)
    124         if (!TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype1)) return false;
    125 
    126         // Now do the full phone number thing. split into parts, seperated by waiting symbol
    127         // and compare them individually
    128         final String[] dataParts1 = data1.toString().split(WAIT_SYMBOL_AS_STRING);
    129         final String[] dataParts2 = data2.toString().split(WAIT_SYMBOL_AS_STRING);
    130         if (dataParts1.length != dataParts2.length) return false;
    131         final PhoneNumberUtil util = PhoneNumberUtil.getInstance();
    132         for (int i = 0; i < dataParts1.length; i++) {
    133             final String dataPart1 = dataParts1[i];
    134             final String dataPart2 = dataParts2[i];
    135 
    136             // substrings equal? shortcut, don't parse
    137             if (TextUtils.equals(dataPart1, dataPart2)) continue;
    138 
    139             // do a full parse of the numbers
    140             switch (util.isNumberMatch(dataPart1, dataPart2)) {
    141                 case NOT_A_NUMBER:
    142                     // don't understand the numbers? let's play it safe
    143                     return false;
    144                 case NO_MATCH:
    145                     return false;
    146                 case EXACT_MATCH:
    147                 case SHORT_NSN_MATCH:
    148                 case NSN_MATCH:
    149                     break;
    150                 default:
    151                     throw new IllegalStateException("Unknown result value from phone number " +
    152                             "library");
    153             }
    154         }
    155         return true;
    156     }
    157 
    158     /**
    159      * Returns true if two {@link Intent}s are both null, or have the same action.
    160      */
    161     public static final boolean areIntentActionEqual(Intent a, Intent b) {
    162         if (a == b) {
    163             return true;
    164         }
    165         if (a == null || b == null) {
    166             return false;
    167         }
    168         return TextUtils.equals(a.getAction(), b.getAction());
    169     }
    170 
    171     /**
    172      * @return The ISO 3166-1 two letters country code of the country the user
    173      *         is in.
    174      */
    175     public static final String getCurrentCountryIso(Context context) {
    176         CountryDetector detector =
    177                 (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR);
    178         return detector.detectCountry().getCountryIso();
    179     }
    180 
    181     public static boolean areContactWritableAccountsAvailable(Context context) {
    182         final List<AccountWithDataSet> accounts =
    183                 AccountTypeManager.getInstance(context).getAccounts(true /* writeable */);
    184         return !accounts.isEmpty();
    185     }
    186 
    187     public static boolean areGroupWritableAccountsAvailable(Context context) {
    188         final List<AccountWithDataSet> accounts =
    189                 AccountTypeManager.getInstance(context).getGroupWritableAccounts();
    190         return !accounts.isEmpty();
    191     }
    192 
    193     /**
    194      * Returns the intent to launch for the given invitable account type and contact lookup URI.
    195      * This will return null if the account type is not invitable (i.e. there is no
    196      * {@link AccountType#getInviteContactActivityClassName()} or
    197      * {@link AccountType#resPackageName}).
    198      */
    199     public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) {
    200         String resPackageName = accountType.resPackageName;
    201         String className = accountType.getInviteContactActivityClassName();
    202         if (TextUtils.isEmpty(resPackageName) || TextUtils.isEmpty(className)) {
    203             return null;
    204         }
    205         Intent intent = new Intent();
    206         intent.setClassName(resPackageName, className);
    207 
    208         intent.setAction(ContactsContract.Intents.INVITE_CONTACT);
    209 
    210         // Data is the lookup URI.
    211         intent.setData(lookupUri);
    212         return intent;
    213     }
    214 
    215     /**
    216      * Returns a header view based on the R.layout.list_separator, where the
    217      * containing {@link TextView} is set using the given textResourceId.
    218      */
    219     public static View createHeaderView(Context context, int textResourceId) {
    220         View view = View.inflate(context, R.layout.list_separator, null);
    221         TextView textView = (TextView) view.findViewById(R.id.title);
    222         textView.setText(context.getString(textResourceId));
    223         return view;
    224     }
    225 
    226     /**
    227      * Returns the {@link Rect} with left, top, right, and bottom coordinates
    228      * that are equivalent to the given {@link View}'s bounds. This is equivalent to how the
    229      * target {@link Rect} is calculated in {@link QuickContact#showQuickContact}.
    230      */
    231     public static Rect getTargetRectFromView(Context context, View view) {
    232         final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
    233         final int[] pos = new int[2];
    234         view.getLocationOnScreen(pos);
    235 
    236         final Rect rect = new Rect();
    237         rect.left = (int) (pos[0] * appScale + 0.5f);
    238         rect.top = (int) (pos[1] * appScale + 0.5f);
    239         rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f);
    240         rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f);
    241         return rect;
    242     }
    243 }
    244