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