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 compareTo(LookupKeySegment another)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 */ getAccountHashCode(String accountTypeWithDataSet, String accountName)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 appendToLookupKey(StringBuilder lookupKey, String accountTypeWithDataSet, String accountName, long rawContactId, String sourceId, String displayName)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 appendEscapedSourceId(StringBuilder sb, String sourceId)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 parse(String lookupKey)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