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.providers.contacts;
     18 
     19 import java.util.ArrayList;
     20 
     21 /**
     22  * Contacts lookup key. Used for generation and parsing of contact lookup keys as well
     23  * as doing the actual lookup.
     24  */
     25 public class ContactLookupKey {
     26 
     27     public static final int LOOKUP_TYPE_SOURCE_ID = 0;
     28     public static final int LOOKUP_TYPE_DISPLAY_NAME = 1;
     29     public static final int LOOKUP_TYPE_RAW_CONTACT_ID = 2;
     30     public static final int LOOKUP_TYPE_PROFILE = 3;
     31 
     32     // The Profile contact will always have a lookup key of "profile".
     33     public static final String PROFILE_LOOKUP_KEY = "profile";
     34 
     35     public static class LookupKeySegment implements Comparable<LookupKeySegment> {
     36         public int accountHashCode;
     37         public int lookupType;
     38         public String rawContactId;
     39         public String key;
     40         public long contactId;
     41 
     42         public int compareTo(LookupKeySegment another) {
     43             if (contactId > another.contactId) {
     44                 return -1;
     45             }
     46             if (contactId < another.contactId) {
     47                 return 1;
     48             }
     49             return 0;
     50         }
     51     }
     52 
     53     /**
     54      * Returns a short hash code that functions as an additional precaution against the exceedingly
     55      * improbable collision between sync IDs in different accounts.
     56      */
     57     public static int getAccountHashCode(String accountTypeWithDataSet, String accountName) {
     58         if (accountTypeWithDataSet == null || accountName == null) {
     59             return 0;
     60         }
     61 
     62         return (accountTypeWithDataSet.hashCode() ^ accountName.hashCode()) & 0xFFF;
     63     }
     64 
     65     public static void appendToLookupKey(StringBuilder lookupKey, String accountTypeWithDataSet,
     66             String accountName, long rawContactId, String sourceId,
     67             String displayName) {
     68         if (displayName == null) {
     69             displayName = "";
     70         }
     71 
     72         if (lookupKey.length() != 0) {
     73             lookupKey.append(".");
     74         }
     75 
     76         lookupKey.append(getAccountHashCode(accountTypeWithDataSet, accountName));
     77         if (sourceId == null) {
     78             lookupKey.append('r').append(rawContactId).append('-').append(
     79                     NameNormalizer.normalize(displayName));
     80         } else {
     81             int pos = lookupKey.length();
     82             lookupKey.append('i');
     83             if (appendEscapedSourceId(lookupKey, sourceId)) {
     84                 lookupKey.setCharAt(pos, 'e');
     85             }
     86         }
     87     }
     88 
     89     private static boolean appendEscapedSourceId(StringBuilder sb, String sourceId) {
     90         boolean escaped = false;
     91         int start = 0;
     92         while (true) {
     93             int index = sourceId.indexOf('.', start);
     94             if (index == -1) {
     95                 sb.append(sourceId, start, sourceId.length());
     96                 break;
     97             }
     98 
     99             escaped = true;
    100             sb.append(sourceId, start, index);
    101             sb.append("..");
    102             start = index + 1;
    103         }
    104         return escaped;
    105     }
    106 
    107     public ArrayList<LookupKeySegment> parse(String lookupKey) {
    108         ArrayList<LookupKeySegment> list = new ArrayList<LookupKeySegment>();
    109 
    110         // If the lookup key is for the profile, just return a segment list indicating that.  The
    111         // caller should already be in a context in which the only contact in the database is the
    112         // user's profile.
    113         if (PROFILE_LOOKUP_KEY.equals(lookupKey)) {
    114             LookupKeySegment profileSegment = new LookupKeySegment();
    115             profileSegment.lookupType = LOOKUP_TYPE_PROFILE;
    116             list.add(profileSegment);
    117             return list;
    118         }
    119 
    120         String string = lookupKey;
    121         int offset = 0;
    122         int length = string.length();
    123         int hashCode = 0;
    124         int lookupType = -1;
    125         boolean escaped = false;
    126         String rawContactId = null;
    127         String key;
    128 
    129         while (offset < length) {
    130             char c = 0;
    131 
    132             // Parse account hash code
    133             hashCode = 0;
    134             while (offset < length) {
    135                 c = string.charAt(offset++);
    136                 if (c < '0' || c > '9') {
    137                     break;
    138                 }
    139                 hashCode = hashCode * 10 + (c - '0');
    140             }
    141 
    142             // Parse segment type
    143             if (c == 'i') {
    144                 lookupType = LOOKUP_TYPE_SOURCE_ID;
    145                 escaped = false;
    146             } else if (c == 'e') {
    147                 lookupType = LOOKUP_TYPE_SOURCE_ID;
    148                 escaped = true;
    149             } else if (c == 'n') {
    150                 lookupType = LOOKUP_TYPE_DISPLAY_NAME;
    151             } else if (c == 'r') {
    152                 lookupType = LOOKUP_TYPE_RAW_CONTACT_ID;
    153             } else if (c == 'c') {
    154                     throw new IllegalArgumentException(
    155                             "Work contact lookup key is not accepted here: " + lookupKey);
    156             } else {
    157                 throw new IllegalArgumentException("Invalid lookup id: " + lookupKey);
    158             }
    159 
    160             // Parse the source ID or normalized display name
    161             switch (lookupType) {
    162                 case LOOKUP_TYPE_SOURCE_ID: {
    163                     if (escaped) {
    164                         StringBuffer sb = new StringBuffer();
    165                         while (offset < length) {
    166                             c = string.charAt(offset++);
    167 
    168                             if (c == '.') {
    169                                 if (offset == length) {
    170                                     throw new IllegalArgumentException("Invalid lookup id: " +
    171                                             lookupKey);
    172                                 }
    173                                 c = string.charAt(offset);
    174 
    175                                 if (c == '.') {
    176                                     sb.append('.');
    177                                     offset++;
    178                                 } else {
    179                                     break;
    180                                 }
    181                             } else {
    182                                 sb.append(c);
    183                             }
    184                         }
    185                         key = sb.toString();
    186                     } else {
    187                         int start = offset;
    188                         while (offset < length) {
    189                             c = string.charAt(offset++);
    190                             if (c == '.') {
    191                                 break;
    192                             }
    193                         }
    194                         if (offset == length) {
    195                             key = string.substring(start);
    196                         } else {
    197                             key = string.substring(start, offset - 1);
    198                         }
    199                     }
    200                     break;
    201                 }
    202                 case LOOKUP_TYPE_DISPLAY_NAME: {
    203                     int start = offset;
    204                     while (offset < length) {
    205                         c = string.charAt(offset++);
    206                         if (c == '.') {
    207                             break;
    208                         }
    209                     }
    210                     if (offset == length) {
    211                         key = string.substring(start);
    212                     } else {
    213                         key = string.substring(start, offset - 1);
    214                     }
    215                     break;
    216                 }
    217                 case LOOKUP_TYPE_RAW_CONTACT_ID: {
    218                     int dash = -1;
    219                     int start = offset;
    220                     while (offset < length) {
    221                         c = string.charAt(offset);
    222                         if (c == '-' && dash == -1) {
    223                             dash = offset;
    224                         }
    225                         offset++;
    226                         if (c == '.') {
    227                             break;
    228                         }
    229                     }
    230                     if (dash != -1) {
    231                         rawContactId = string.substring(start, dash);
    232                         start = dash + 1;
    233                     }
    234                     if (offset == length) {
    235                         key = string.substring(start);
    236                     } else {
    237                         key = string.substring(start, offset - 1);
    238                     }
    239                     break;
    240                 }
    241                 default:
    242                     // Will never happen
    243                     throw new IllegalStateException();
    244             }
    245 
    246             LookupKeySegment segment = new LookupKeySegment();
    247             segment.accountHashCode = hashCode;
    248             segment.lookupType = lookupType;
    249             segment.rawContactId = rawContactId;
    250             segment.key = key;
    251             segment.contactId = -1;
    252             list.add(segment);
    253         }
    254 
    255         return list;
    256     }
    257 }
    258