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 17package com.android.providers.contacts; 18 19import android.net.Uri; 20 21import 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 */ 27public 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