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 
     33     public static class LookupKeySegment implements Comparable<LookupKeySegment> {
     34         public int accountHashCode;
     35         public int lookupType;
     36         public String rawContactId;
     37         public String key;
     38         public long contactId;
     39 
     40         public int compareTo(LookupKeySegment another) {
     41             if (contactId > another.contactId) {
     42                 return -1;
     43             }
     44             if (contactId < another.contactId) {
     45                 return 1;
     46             }
     47             return 0;
     48         }
     49     }
     50 
     51     /**
     52      * Returns a short hash code that functions as an additional precaution against the exceedingly
     53      * improbable collision between sync IDs in different accounts.
     54      */
     55     public static int getAccountHashCode(String accountType, String accountName) {
     56         if (accountType == null || accountName == null) {
     57             return 0;
     58         }
     59 
     60         return (accountType.hashCode() ^ accountName.hashCode()) & 0xFFF;
     61     }
     62 
     63     public static void appendToLookupKey(StringBuilder lookupKey, String accountType,
     64             String accountName, long rawContactId, String sourceId, String displayName) {
     65         if (displayName == null) {
     66             displayName = "";
     67         }
     68 
     69         if (lookupKey.length() != 0) {
     70             lookupKey.append(".");
     71         }
     72 
     73         lookupKey.append(getAccountHashCode(accountType, accountName));
     74         if (sourceId == null) {
     75             lookupKey.append('r').append(rawContactId).append('-').append(
     76                     NameNormalizer.normalize(displayName));
     77         } else {
     78             int pos = lookupKey.length();
     79             lookupKey.append('i');
     80             if (appendEscapedSourceId(lookupKey, sourceId)) {
     81                 lookupKey.setCharAt(pos, 'e');
     82             }
     83         }
     84     }
     85 
     86     private static boolean appendEscapedSourceId(StringBuilder sb, String sourceId) {
     87         boolean escaped = false;
     88         int start = 0;
     89         while (true) {
     90             int index = sourceId.indexOf('.', start);
     91             if (index == -1) {
     92                 sb.append(sourceId, start, sourceId.length());
     93                 break;
     94             }
     95 
     96             escaped = true;
     97             sb.append(sourceId, start, index);
     98             sb.append("..");
     99             start = index + 1;
    100         }
    101         return escaped;
    102     }
    103 
    104     public ArrayList<LookupKeySegment> parse(String lookupKey) {
    105         ArrayList<LookupKeySegment> list = new ArrayList<LookupKeySegment>();
    106 
    107         String string = Uri.decode(lookupKey);
    108         int offset = 0;
    109         int length = string.length();
    110         int hashCode = 0;
    111         int lookupType = -1;
    112         boolean escaped = false;
    113         String rawContactId = null;
    114         String key;
    115 
    116         while (offset < length) {
    117             char c = 0;
    118 
    119             // Parse account hash code
    120             hashCode = 0;
    121             while (offset < length) {
    122                 c = string.charAt(offset++);
    123                 if (c < '0' || c > '9') {
    124                     break;
    125                 }
    126                 hashCode = hashCode * 10 + (c - '0');
    127             }
    128 
    129             // Parse segment type
    130             if (c == 'i') {
    131                 lookupType = LOOKUP_TYPE_SOURCE_ID;
    132                 escaped = false;
    133             } else if (c == 'e') {
    134                 lookupType = LOOKUP_TYPE_SOURCE_ID;
    135                 escaped = true;
    136             } else if (c == 'n') {
    137                 lookupType = LOOKUP_TYPE_DISPLAY_NAME;
    138             } else if (c == 'r') {
    139                 lookupType = LOOKUP_TYPE_RAW_CONTACT_ID;
    140             } else {
    141                 throw new IllegalArgumentException("Invalid lookup id: " + lookupKey);
    142             }
    143 
    144             // Parse the source ID or normalized display name
    145             switch (lookupType) {
    146                 case LOOKUP_TYPE_SOURCE_ID: {
    147                     if (escaped) {
    148                         StringBuffer sb = new StringBuffer();
    149                         while (offset < length) {
    150                             c = string.charAt(offset++);
    151 
    152                             if (c == '.') {
    153                                 if (offset == length) {
    154                                     throw new IllegalArgumentException("Invalid lookup id: " +
    155                                             lookupKey);
    156                                 }
    157                                 c = string.charAt(offset);
    158 
    159                                 if (c == '.') {
    160                                     sb.append('.');
    161                                     offset++;
    162                                 } else {
    163                                     break;
    164                                 }
    165                             } else {
    166                                 sb.append(c);
    167                             }
    168                         }
    169                         key = sb.toString();
    170                     } else {
    171                         int start = offset;
    172                         while (offset < length) {
    173                             c = string.charAt(offset++);
    174                             if (c == '.') {
    175                                 break;
    176                             }
    177                         }
    178                         if (offset == length) {
    179                             key = string.substring(start);
    180                         } else {
    181                             key = string.substring(start, offset - 1);
    182                         }
    183                     }
    184                     break;
    185                 }
    186                 case LOOKUP_TYPE_DISPLAY_NAME: {
    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                     break;
    200                 }
    201                 case LOOKUP_TYPE_RAW_CONTACT_ID: {
    202                     int dash = -1;
    203                     int start = offset;
    204                     while (offset < length) {
    205                         c = string.charAt(offset);
    206                         if (c == '-' && dash == -1) {
    207                             dash = offset;
    208                         }
    209                         offset++;
    210                         if (c == '.') {
    211                             break;
    212                         }
    213                     }
    214                     if (dash != -1) {
    215                         rawContactId = string.substring(start, dash);
    216                         start = dash + 1;
    217                     }
    218                     if (offset == length) {
    219                         key = string.substring(start);
    220                     } else {
    221                         key = string.substring(start, offset - 1);
    222                     }
    223                     break;
    224                 }
    225                 default:
    226                     // Will never happen
    227                     throw new IllegalStateException();
    228             }
    229 
    230             LookupKeySegment segment = new LookupKeySegment();
    231             segment.accountHashCode = hashCode;
    232             segment.lookupType = lookupType;
    233             segment.rawContactId = rawContactId;
    234             segment.key = key;
    235             segment.contactId = -1;
    236             list.add(segment);
    237         }
    238 
    239         return list;
    240     }
    241 }
    242