Home | History | Annotate | Download | only in common
      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;
     18 
     19 import android.content.Intent;
     20 import android.graphics.Rect;
     21 import android.net.Uri;
     22 import android.provider.ContactsContract;
     23 import android.telephony.PhoneNumberUtils;
     24 import android.text.TextUtils;
     25 import android.view.View;
     26 import com.android.contacts.common.model.account.AccountType;
     27 import com.google.i18n.phonenumbers.NumberParseException;
     28 import com.google.i18n.phonenumbers.PhoneNumberUtil;
     29 
     30 /** Shared static contact utility methods. */
     31 public class MoreContactUtils {
     32 
     33   private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);
     34 
     35   /**
     36    * Returns true if two data with mimetypes which represent values in contact entries are
     37    * considered equal for collapsing in the GUI. For caller-id, use {@link
     38    * android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)} instead
     39    */
     40   public static boolean shouldCollapse(
     41       CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2) {
     42     // different mimetypes? don't collapse
     43     if (!TextUtils.equals(mimetype1, mimetype2)) {
     44       return false;
     45     }
     46 
     47     // exact same string? good, bail out early
     48     if (TextUtils.equals(data1, data2)) {
     49       return true;
     50     }
     51 
     52     // so if either is null, these two must be different
     53     if (data1 == null || data2 == null) {
     54       return false;
     55     }
     56 
     57     // if this is not about phone numbers, we know this is not a match (of course, some
     58     // mimetypes could have more sophisticated matching is the future, e.g. addresses)
     59     if (!TextUtils.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, mimetype1)) {
     60       return false;
     61     }
     62 
     63     return shouldCollapsePhoneNumbers(data1.toString(), data2.toString());
     64   }
     65 
     66   // TODO: Move this to PhoneDataItem.shouldCollapse override
     67   private static boolean shouldCollapsePhoneNumbers(String number1, String number2) {
     68     // Work around to address b/20724444. We want to distinguish between #555, *555 and 555.
     69     // This makes no attempt to distinguish between 555 and 55*5, since 55*5 is an improbable
     70     // number. PhoneNumberUtil already distinguishes between 555 and 55#5.
     71     if (number1.contains("#") != number2.contains("#")
     72         || number1.contains("*") != number2.contains("*")) {
     73       return false;
     74     }
     75 
     76     // Now do the full phone number thing. split into parts, separated by waiting symbol
     77     // and compare them individually
     78     final String[] dataParts1 = number1.split(WAIT_SYMBOL_AS_STRING);
     79     final String[] dataParts2 = number2.split(WAIT_SYMBOL_AS_STRING);
     80     if (dataParts1.length != dataParts2.length) {
     81       return false;
     82     }
     83     final PhoneNumberUtil util = PhoneNumberUtil.getInstance();
     84     for (int i = 0; i < dataParts1.length; i++) {
     85       // Match phone numbers represented by keypad letters, in which case prefer the
     86       // phone number with letters.
     87       final String dataPart1 = PhoneNumberUtils.convertKeypadLettersToDigits(dataParts1[i]);
     88       final String dataPart2 = dataParts2[i];
     89 
     90       // substrings equal? shortcut, don't parse
     91       if (TextUtils.equals(dataPart1, dataPart2)) {
     92         continue;
     93       }
     94 
     95       // do a full parse of the numbers
     96       final PhoneNumberUtil.MatchType result = util.isNumberMatch(dataPart1, dataPart2);
     97       switch (result) {
     98         case NOT_A_NUMBER:
     99           // don't understand the numbers? let's play it safe
    100           return false;
    101         case NO_MATCH:
    102           return false;
    103         case EXACT_MATCH:
    104           break;
    105         case NSN_MATCH:
    106           try {
    107             // For NANP phone numbers, match when one has +1 and the other does not.
    108             // In this case, prefer the +1 version.
    109             if (util.parse(dataPart1, null).getCountryCode() == 1) {
    110               // At this point, the numbers can be either case 1 or 2 below....
    111               //
    112               // case 1)
    113               // +14155551212    <--- country code 1
    114               //  14155551212    <--- 1 is trunk prefix, not country code
    115               //
    116               // and
    117               //
    118               // case 2)
    119               // +14155551212
    120               //   4155551212
    121               //
    122               // From b/7519057, case 2 needs to be equal.  But also that bug, case 3
    123               // below should not be equal.
    124               //
    125               // case 3)
    126               // 14155551212
    127               //  4155551212
    128               //
    129               // So in order to make sure transitive equality is valid, case 1 cannot
    130               // be equal.  Otherwise, transitive equality breaks and the following
    131               // would all be collapsed:
    132               //   4155551212  |
    133               //  14155551212  |---->   +14155551212
    134               // +14155551212  |
    135               //
    136               // With transitive equality, the collapsed values should be:
    137               //   4155551212  |         14155551212
    138               //  14155551212  |---->   +14155551212
    139               // +14155551212  |
    140 
    141               // Distinguish between case 1 and 2 by checking for trunk prefix '1'
    142               // at the start of number 2.
    143               if (dataPart2.trim().charAt(0) == '1') {
    144                 // case 1
    145                 return false;
    146               }
    147               break;
    148             }
    149           } catch (NumberParseException e) {
    150             // This is the case where the first number does not have a country code.
    151             // examples:
    152             // (123) 456-7890   &   123-456-7890  (collapse)
    153             // 0049 (8092) 1234   &   +49/80921234  (unit test says do not collapse)
    154 
    155             // Check the second number.  If it also does not have a country code, then
    156             // we should collapse.  If it has a country code, then it's a different
    157             // number and we should not collapse (this conclusion is based on an
    158             // existing unit test).
    159             try {
    160               util.parse(dataPart2, null);
    161             } catch (NumberParseException e2) {
    162               // Number 2 also does not have a country.  Collapse.
    163               break;
    164             }
    165           }
    166           return false;
    167         case SHORT_NSN_MATCH:
    168           return false;
    169         default:
    170           throw new IllegalStateException("Unknown result value from phone number " + "library");
    171       }
    172     }
    173     return true;
    174   }
    175 
    176   /**
    177    * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates that
    178    * are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to how the
    179    * target {@link android.graphics.Rect} is calculated in {@link
    180    * android.provider.ContactsContract.QuickContact#showQuickContact}.
    181    */
    182   public static Rect getTargetRectFromView(View view) {
    183     final int[] pos = new int[2];
    184     view.getLocationOnScreen(pos);
    185 
    186     final Rect rect = new Rect();
    187     rect.left = pos[0];
    188     rect.top = pos[1];
    189     rect.right = pos[0] + view.getWidth();
    190     rect.bottom = pos[1] + view.getHeight();
    191     return rect;
    192   }
    193 
    194   /**
    195    * Returns the intent to launch for the given invitable account type and contact lookup URI. This
    196    * will return null if the account type is not invitable (i.e. there is no {@link
    197    * AccountType#getInviteContactActivityClassName()} or {@link
    198    * AccountType#syncAdapterPackageName}).
    199    */
    200   public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) {
    201     String syncAdapterPackageName = accountType.syncAdapterPackageName;
    202     String className = accountType.getInviteContactActivityClassName();
    203     if (TextUtils.isEmpty(syncAdapterPackageName) || TextUtils.isEmpty(className)) {
    204       return null;
    205     }
    206     Intent intent = new Intent();
    207     intent.setClassName(syncAdapterPackageName, className);
    208 
    209     intent.setAction(ContactsContract.Intents.INVITE_CONTACT);
    210 
    211     // Data is the lookup URI.
    212     intent.setData(lookupUri);
    213     return intent;
    214   }
    215 }
    216