Changeset View
Changeset View
Standalone View
Standalone View
src/org/kde/kdeconnect/Helpers/ContactsHelper.java
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | * Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com> | 2 | * Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com> | ||
3 | * Copyright 2018 Simon Redman <simon@ergotech.com> | ||||
3 | * | 4 | * | ||
4 | * This program is free software; you can redistribute it and/or | 5 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | 6 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation; either version 2 of | 7 | * published by the Free Software Foundation; either version 2 of | ||
7 | * the License or (at your option) version 3 or any later version | 8 | * the License or (at your option) version 3 or any later version | ||
8 | * accepted by the membership of KDE e.V. (or its successor approved | 9 | * accepted by the membership of KDE e.V. (or its successor approved | ||
9 | * by the membership of KDE e.V.), which shall act as a proxy | 10 | * by the membership of KDE e.V.), which shall act as a proxy | ||
10 | * defined in Section 14 of version 3 of the license. | 11 | * defined in Section 14 of version 3 of the license. | ||
Show All 11 Lines | |||||
22 | 23 | | |||
23 | import android.annotation.TargetApi; | 24 | import android.annotation.TargetApi; | ||
24 | import android.content.Context; | 25 | import android.content.Context; | ||
25 | import android.database.Cursor; | 26 | import android.database.Cursor; | ||
26 | import android.net.Uri; | 27 | import android.net.Uri; | ||
27 | import android.os.Build; | 28 | import android.os.Build; | ||
28 | import android.provider.ContactsContract; | 29 | import android.provider.ContactsContract; | ||
29 | import android.provider.ContactsContract.PhoneLookup; | 30 | import android.provider.ContactsContract.PhoneLookup; | ||
31 | import android.support.annotation.RequiresApi; | ||||
32 | import android.support.v4.util.LongSparseArray; | ||||
30 | import android.util.Base64; | 33 | import android.util.Base64; | ||
31 | import android.util.Base64OutputStream; | 34 | import android.util.Base64OutputStream; | ||
32 | import android.util.Log; | 35 | import android.util.Log; | ||
33 | 36 | | |||
37 | import java.io.BufferedReader; | ||||
34 | import java.io.ByteArrayOutputStream; | 38 | import java.io.ByteArrayOutputStream; | ||
39 | import java.io.IOException; | ||||
35 | import java.io.InputStream; | 40 | import java.io.InputStream; | ||
41 | import java.io.InputStreamReader; | ||||
42 | import java.util.ArrayList; | ||||
43 | import java.util.Arrays; | ||||
44 | import java.util.Collection; | ||||
36 | import java.util.HashMap; | 45 | import java.util.HashMap; | ||
46 | import java.util.HashSet; | ||||
47 | import java.util.List; | ||||
37 | import java.util.Map; | 48 | import java.util.Map; | ||
49 | import java.util.Set; | ||||
38 | 50 | | |||
39 | public class ContactsHelper { | 51 | public class ContactsHelper { | ||
40 | 52 | | |||
41 | 53 | | |||
42 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) | 54 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) | ||
55 | /** | ||||
56 | * Lookup the name and photoID of a contact given a phone number | ||||
57 | */ | ||||
43 | public static Map<String, String> phoneNumberLookup(Context context, String number) { | 58 | public static Map<String, String> phoneNumberLookup(Context context, String number) { | ||
44 | 59 | | |||
45 | //Log.e("PhoneNumberLookup", number); | 60 | //Log.e("PhoneNumberLookup", number); | ||
46 | 61 | | |||
47 | Map<String, String> contactInfo = new HashMap<>(); | 62 | Map<String, String> contactInfo = new HashMap<>(); | ||
48 | 63 | | |||
49 | Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); | 64 | Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); | ||
50 | Cursor cursor = null; | 65 | Cursor cursor; | ||
51 | try { | 66 | try { | ||
52 | cursor = context.getContentResolver().query( | 67 | cursor = context.getContentResolver().query( | ||
53 | uri, | 68 | uri, | ||
54 | new String[]{ | 69 | new String[]{ | ||
55 | PhoneLookup.DISPLAY_NAME, | 70 | PhoneLookup.DISPLAY_NAME, | ||
56 | ContactsContract.PhoneLookup.PHOTO_URI | 71 | ContactsContract.PhoneLookup.PHOTO_URI | ||
57 | /*, PhoneLookup.TYPE | 72 | /*, PhoneLookup.TYPE | ||
58 | , PhoneLookup.LABEL | 73 | , PhoneLookup.LABEL | ||
Show All 13 Lines | 82 | if (cursor != null && cursor.moveToFirst()) { | |||
72 | 87 | | |||
73 | nameIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI); | 88 | nameIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI); | ||
74 | if (nameIndex != -1) { | 89 | if (nameIndex != -1) { | ||
75 | contactInfo.put("photoID", cursor.getString(nameIndex)); | 90 | contactInfo.put("photoID", cursor.getString(nameIndex)); | ||
76 | } | 91 | } | ||
77 | 92 | | |||
78 | try { | 93 | try { | ||
79 | cursor.close(); | 94 | cursor.close(); | ||
80 | } catch (Exception e) { | 95 | } catch (Exception ignored) { | ||
81 | } | 96 | } | ||
82 | 97 | | |||
83 | if (!contactInfo.isEmpty()) { | 98 | if (!contactInfo.isEmpty()) { | ||
84 | return contactInfo; | 99 | return contactInfo; | ||
85 | } | 100 | } | ||
86 | } | 101 | } | ||
87 | 102 | | |||
88 | return contactInfo; | 103 | return contactInfo; | ||
89 | } | 104 | } | ||
90 | 105 | | |||
91 | public static String photoId64Encoded(Context context, String photoId) { | 106 | public static String photoId64Encoded(Context context, String photoId) { | ||
92 | if (photoId == null) { | 107 | if (photoId == null) { | ||
93 | return ""; | 108 | return ""; | ||
94 | } | 109 | } | ||
95 | Uri photoUri = Uri.parse(photoId); | 110 | Uri photoUri = Uri.parse(photoId); | ||
96 | 111 | | |||
97 | InputStream input = null; | 112 | InputStream input = null; | ||
98 | Base64OutputStream output = null; | 113 | Base64OutputStream output = null; | ||
99 | try { | 114 | try { | ||
100 | ByteArrayOutputStream encodedPhoto = new ByteArrayOutputStream(); | 115 | ByteArrayOutputStream encodedPhoto = new ByteArrayOutputStream(); | ||
101 | output = new Base64OutputStream(encodedPhoto, Base64.DEFAULT); | 116 | output = new Base64OutputStream(encodedPhoto, Base64.DEFAULT); | ||
102 | input = context.getContentResolver().openInputStream(photoUri); | 117 | input = context.getContentResolver().openInputStream(photoUri); | ||
103 | byte[] buffer = new byte[1024]; | 118 | byte[] buffer = new byte[1024]; | ||
104 | int len; | 119 | int len; | ||
120 | //noinspection ConstantConditions | ||||
105 | while ((len = input.read(buffer)) != -1) { | 121 | while ((len = input.read(buffer)) != -1) { | ||
106 | output.write(buffer, 0, len); | 122 | output.write(buffer, 0, len); | ||
107 | } | 123 | } | ||
108 | return encodedPhoto.toString(); | 124 | return encodedPhoto.toString(); | ||
109 | } catch (Exception ex) { | 125 | } catch (Exception ex) { | ||
110 | Log.e("ContactsHelper", ex.toString()); | 126 | Log.e("ContactsHelper", ex.toString()); | ||
111 | return ""; | 127 | return ""; | ||
112 | } finally { | 128 | } finally { | ||
113 | try { | 129 | try { | ||
130 | //noinspection ConstantConditions | ||||
114 | input.close(); | 131 | input.close(); | ||
115 | } catch (Exception ignored) { | 132 | } catch (Exception ignored) { | ||
116 | } | 133 | } | ||
117 | 134 | | |||
118 | try { | 135 | try { | ||
136 | //noinspection ConstantConditions | ||||
119 | output.close(); | 137 | output.close(); | ||
120 | } catch (Exception ignored) { | 138 | } catch (Exception ignored) { | ||
121 | } | 139 | } | ||
122 | 140 | | |||
123 | } | 141 | } | ||
124 | } | 142 | } | ||
143 | | ||||
144 | /** | ||||
145 | * Return all the NAME_RAW_CONTACT_IDS which contribute an entry to a Contact in the database | ||||
146 | * <p> | ||||
147 | * If the user has, for example, joined several contacts, on the phone, the IDs returned will | ||||
148 | * be representative of the joined contact | ||||
149 | * <p> | ||||
150 | * See here: https://developer.android.com/reference/android/provider/ContactsContract.Contacts.html | ||||
151 | * for more information about the connection between contacts and raw contacts | ||||
152 | * | ||||
153 | * @param context android.content.Context running the request | ||||
154 | * @return List of each NAME_RAW_CONTACT_ID in the Contacts database | ||||
155 | */ | ||||
156 | public static List<uID> getAllContactContactIDs(Context context) { | ||||
157 | ArrayList<uID> toReturn = new ArrayList<>(); | ||||
158 | | ||||
159 | // Define the columns we want to read from the Contacts database | ||||
160 | final String[] projection = new String[]{ | ||||
161 | ContactsContract.Contacts.LOOKUP_KEY | ||||
162 | }; | ||||
163 | | ||||
164 | Uri contactsUri = ContactsContract.Contacts.CONTENT_URI; | ||||
165 | Cursor contactsCursor = context.getContentResolver().query( | ||||
166 | contactsUri, | ||||
167 | projection, | ||||
168 | null, null, null); | ||||
169 | if (contactsCursor != null && contactsCursor.moveToFirst()) { | ||||
170 | do { | ||||
171 | uID contactID; | ||||
172 | | ||||
173 | int idIndex = contactsCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); | ||||
174 | if (idIndex != -1) { | ||||
175 | contactID = new uID(contactsCursor.getString(idIndex)); | ||||
176 | } else { | ||||
177 | // Something went wrong with this contact | ||||
178 | // If you are experiencing this, please open a bug report indicating how you got here | ||||
179 | Log.e("ContactsHelper", "Got a contact which does not have a LOOKUP_KEY"); | ||||
180 | continue; | ||||
125 | } | 181 | } | ||
126 | 182 | | |||
183 | toReturn.add(contactID); | ||||
184 | } while (contactsCursor.moveToNext()); | ||||
185 | try { | ||||
186 | contactsCursor.close(); | ||||
187 | } catch (Exception ignored) { | ||||
188 | } | ||||
189 | } | ||||
190 | | ||||
191 | return toReturn; | ||||
192 | } | ||||
193 | | ||||
194 | /** | ||||
195 | * Get VCards using the batch database query which requires Android API 21 | ||||
196 | * | ||||
197 | * @param context android.content.Context running the request | ||||
198 | * @param IDs collection of raw contact IDs to look up | ||||
199 | * @param lookupKeys | ||||
200 | * @return Mapping of raw contact IDs to corresponding VCard | ||||
201 | */ | ||||
202 | @SuppressWarnings("ALL") // Since this method is busted anyway | ||||
203 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
204 | @Deprecated | ||||
205 | protected static Map<Long, VCardBuilder> getVCardsFast(Context context, Collection<Long> IDs, Map<Long, String> lookupKeys) { | ||||
206 | LongSparseArray<VCardBuilder> toReturn = new LongSparseArray<>(); | ||||
207 | StringBuilder keys = new StringBuilder(); | ||||
208 | | ||||
209 | List<Long> orderedIDs = new ArrayList<>(IDs); | ||||
210 | | ||||
211 | for (Long ID : orderedIDs) { | ||||
212 | String key = lookupKeys.get(ID); | ||||
213 | keys.append(key); | ||||
214 | keys.append(':'); | ||||
215 | } | ||||
216 | | ||||
217 | // Remove trailing ':' | ||||
218 | keys.deleteCharAt(keys.length() - 1); | ||||
219 | | ||||
220 | Uri vcardURI = Uri.withAppendedPath( | ||||
221 | ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI, | ||||
222 | Uri.encode(keys.toString())); | ||||
223 | | ||||
224 | InputStream input; | ||||
225 | StringBuilder vcardJumble = new StringBuilder(); | ||||
226 | try { | ||||
227 | input = context.getContentResolver().openInputStream(vcardURI); | ||||
228 | | ||||
229 | BufferedReader bufferedInput = new BufferedReader(new InputStreamReader(input)); | ||||
230 | String line; | ||||
231 | | ||||
232 | while ((line = bufferedInput.readLine()) != null) { | ||||
233 | vcardJumble.append(line).append('\n'); | ||||
234 | } | ||||
235 | } catch (IOException e) { | ||||
236 | // If you are experiencing this, please open a bug report indicating how you got here | ||||
237 | e.printStackTrace(); | ||||
238 | } | ||||
239 | | ||||
240 | // At this point we are screwed: | ||||
241 | // There is no way to figure out, given the lookup we just made, which VCard belonges | ||||
242 | // to which ID. They appear to be in the same order as the request was made, but this | ||||
243 | // is (provably) unreliable. I am leaving this code in case it is useful, but unless | ||||
244 | // Android improves their API there is nothing we can do with it | ||||
245 | | ||||
246 | return null; | ||||
247 | } | ||||
248 | | ||||
249 | /** | ||||
250 | * Get VCards using serial database lookups. This is tragically slow, but the faster method using | ||||
251 | * | ||||
252 | * There is a faster API specified using ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI, | ||||
253 | * but there does not seem to be a way to figure out which ID resulted in which VCard using that API | ||||
254 | * | ||||
255 | * @param context android.content.Context running the request | ||||
256 | * @param IDs collection of uIDs to look up | ||||
257 | * @return Mapping of uIDs to the corresponding VCard | ||||
258 | */ | ||||
259 | @SuppressWarnings("UnnecessaryContinue") | ||||
260 | protected static Map<uID, VCardBuilder> getVCardsSlow(Context context, Collection<uID> IDs) { | ||||
261 | Map<uID, VCardBuilder> toReturn = new HashMap<>(); | ||||
262 | | ||||
263 | for (uID ID : IDs) { | ||||
264 | String lookupKey = ID.toString(); | ||||
265 | Uri vcardURI = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey); | ||||
266 | InputStream input; | ||||
267 | try { | ||||
268 | input = context.getContentResolver().openInputStream(vcardURI); | ||||
269 | | ||||
270 | if (input == null) | ||||
271 | { | ||||
272 | throw new NullPointerException("ContentResolver did not give us a stream for the VCard for uID " + ID); | ||||
273 | } | ||||
274 | | ||||
275 | BufferedReader bufferedInput = new BufferedReader(new InputStreamReader(input)); | ||||
276 | | ||||
277 | StringBuilder vcard = new StringBuilder(); | ||||
278 | String line; | ||||
279 | while ((line = bufferedInput.readLine()) != null) { | ||||
280 | vcard.append(line).append('\n'); | ||||
281 | } | ||||
282 | | ||||
283 | toReturn.put(ID, new VCardBuilder(vcard.toString())); | ||||
284 | input.close(); | ||||
285 | } catch (IOException e) { | ||||
286 | // If you are experiencing this, please open a bug report indicating how you got here | ||||
287 | e.printStackTrace(); | ||||
288 | continue; | ||||
289 | } catch (NullPointerException e) | ||||
290 | { | ||||
291 | // If you are experiencing this, please open a bug report indicating how you got here | ||||
292 | e.printStackTrace(); | ||||
293 | } | ||||
294 | } | ||||
295 | | ||||
296 | return toReturn; | ||||
297 | } | ||||
298 | | ||||
299 | /** | ||||
300 | * Get the VCard for every specified raw contact ID | ||||
301 | * | ||||
302 | * @param context android.content.Context running the request | ||||
303 | * @param IDs collection of raw contact IDs to look up | ||||
304 | * @return Mapping of raw contact IDs to the corresponding VCard | ||||
305 | */ | ||||
306 | public static Map<uID, VCardBuilder> getVCardsForContactIDs(Context context, Collection<uID> IDs) { | ||||
307 | return getVCardsSlow(context, IDs); | ||||
308 | } | ||||
309 | | ||||
310 | /** | ||||
311 | * Return a mapping of contact IDs to a map of the requested data from the Contacts database | ||||
312 | * <p> | ||||
313 | * If for some reason there is no row associated with the contact ID in the database, | ||||
314 | * there will not be a corresponding field in the returned map | ||||
315 | * | ||||
316 | * @param context android.content.Context running the request | ||||
317 | * @param IDs collection of contact uIDs to look up | ||||
318 | * @param contactsProjection List of column names to extract, defined in ContactsContract.Contacts | ||||
319 | * @return mapping of contact uIDs to desired values, which are a mapping of column names to the data contained there | ||||
320 | */ | ||||
321 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) // Needed for Cursor.getType(..) | ||||
322 | public static Map<uID, Map<String, Object>> getColumnsFromContactsForIDs(Context context, Collection<uID> IDs, String[] contactsProjection) { | ||||
323 | HashMap<uID, Map<String, Object>> toReturn = new HashMap<>(); | ||||
324 | | ||||
325 | Uri contactsUri = ContactsContract.Contacts.CONTENT_URI; | ||||
326 | | ||||
327 | // Regardless of whether it was requested, we need to look up the uID column | ||||
328 | Set<String> lookupProjection = new HashSet<>(Arrays.asList(contactsProjection)); | ||||
329 | lookupProjection.add(uID.COLUMN); | ||||
330 | | ||||
331 | // We need a selection which looks like "<column> IN(?,?,...?)" with one ? per ID | ||||
332 | StringBuilder contactsSelection = new StringBuilder(uID.COLUMN); | ||||
333 | contactsSelection.append(" IN("); | ||||
334 | | ||||
335 | for (int i = 0; i < IDs.size(); i++) { | ||||
336 | contactsSelection.append("?,"); | ||||
337 | } | ||||
338 | // Remove trailing comma | ||||
339 | contactsSelection.deleteCharAt(contactsSelection.length() - 1); | ||||
340 | contactsSelection.append(")"); | ||||
341 | | ||||
342 | // We need selection arguments as simply a String representation of each ID | ||||
343 | List<String> contactsArgs = new ArrayList<>(); | ||||
344 | for (uID ID : IDs) { | ||||
345 | contactsArgs.add(ID.toString()); | ||||
346 | } | ||||
347 | | ||||
348 | Cursor contactsCursor = context.getContentResolver().query( | ||||
349 | contactsUri, | ||||
350 | lookupProjection.toArray(new String[0]), | ||||
351 | contactsSelection.toString(), | ||||
352 | contactsArgs.toArray(new String[0]), null | ||||
353 | ); | ||||
354 | | ||||
355 | if (contactsCursor != null && contactsCursor.moveToFirst()) { | ||||
356 | do { | ||||
357 | Map<String, Object> requestedData = new HashMap<>(); | ||||
358 | | ||||
359 | int lookupKeyIdx = contactsCursor.getColumnIndexOrThrow(uID.COLUMN); | ||||
360 | String lookupKey = contactsCursor.getString(lookupKeyIdx); | ||||
361 | | ||||
362 | // For each column, collect the data from that column | ||||
363 | for (String column : contactsProjection) { | ||||
364 | int index = contactsCursor.getColumnIndex(column); | ||||
365 | // Since we might be getting various kinds of data, Object is the best we can do | ||||
366 | Object data; | ||||
367 | int type; | ||||
368 | if (index == -1) { | ||||
369 | // This contact didn't have the requested column? Something is very wrong. | ||||
370 | // If you are experiencing this, please open a bug report indicating how you got here | ||||
371 | Log.e("ContactsHelper", "Got a contact which does not have a requested column"); | ||||
372 | continue; | ||||
373 | } | ||||
374 | | ||||
375 | type = contactsCursor.getType(index); | ||||
376 | switch (type) { | ||||
377 | case Cursor.FIELD_TYPE_INTEGER: | ||||
378 | data = contactsCursor.getInt(index); | ||||
379 | break; | ||||
380 | case Cursor.FIELD_TYPE_FLOAT: | ||||
381 | data = contactsCursor.getFloat(index); | ||||
382 | break; | ||||
383 | case Cursor.FIELD_TYPE_STRING: | ||||
384 | data = contactsCursor.getString(index); | ||||
385 | break; | ||||
386 | case Cursor.FIELD_TYPE_BLOB: | ||||
387 | data = contactsCursor.getBlob(index); | ||||
388 | break; | ||||
389 | default: | ||||
390 | Log.e("ContactsHelper", "Got an undefined type of column " + column); | ||||
391 | continue; | ||||
392 | } | ||||
393 | | ||||
394 | requestedData.put(column, data); | ||||
395 | } | ||||
396 | | ||||
397 | toReturn.put(new uID(lookupKey), requestedData); | ||||
398 | } while (contactsCursor.moveToNext()); | ||||
399 | try { | ||||
400 | contactsCursor.close(); | ||||
401 | } catch (Exception ignored) { | ||||
402 | } | ||||
403 | } | ||||
404 | | ||||
405 | return toReturn; | ||||
406 | } | ||||
407 | | ||||
408 | /** | ||||
409 | * This is a cheap ripoff of com.android.vcard.VCardBuilder | ||||
410 | * <p> | ||||
411 | * Maybe in the future that library will be made public and we can switch to using that! | ||||
412 | * <p> | ||||
413 | * The main similarity is the usage of .toString() to produce the finalized VCard and the | ||||
414 | * usage of .appendLine(String, String) to add stuff to the vcard | ||||
415 | */ | ||||
416 | public static class VCardBuilder { | ||||
417 | protected static final String VCARD_END = "END:VCARD"; // Written to terminate the vcard | ||||
418 | protected static final String VCARD_DATA_SEPARATOR = ":"; | ||||
419 | | ||||
420 | final StringBuilder vcardBody; | ||||
421 | | ||||
422 | /** | ||||
423 | * Take a partial vcard as a string and make a VCardBuilder | ||||
424 | * | ||||
425 | * @param vcard vcard to build upon | ||||
426 | */ | ||||
427 | public VCardBuilder(String vcard) { | ||||
428 | // Remove the end tag. We will add it back on in .toString() | ||||
429 | vcard = vcard.substring(0, vcard.indexOf(VCARD_END)); | ||||
430 | | ||||
431 | vcardBody = new StringBuilder(vcard); | ||||
432 | } | ||||
433 | | ||||
434 | /** | ||||
435 | * Appends one line with a given property name and value. | ||||
436 | */ | ||||
437 | public void appendLine(final String propertyName, final String rawValue) { | ||||
438 | vcardBody.append(propertyName) | ||||
439 | .append(VCARD_DATA_SEPARATOR) | ||||
440 | .append(rawValue) | ||||
441 | .append("\n"); | ||||
442 | } | ||||
443 | | ||||
444 | public String toString() { | ||||
445 | return vcardBody.toString() + VCARD_END; | ||||
446 | } | ||||
447 | } | ||||
448 | | ||||
449 | /** | ||||
450 | * Essentially a typedef of the type used for a unique identifier | ||||
451 | */ | ||||
452 | public static class uID { | ||||
453 | /** | ||||
454 | * We use the LOOKUP_KEY column of the Contacts table as a unique ID, since that's what it's | ||||
455 | * for | ||||
456 | */ | ||||
457 | final String contactLookupKey; | ||||
458 | | ||||
459 | /** | ||||
460 | * Which Contacts column this uID is pulled from | ||||
461 | */ | ||||
462 | static final String COLUMN = ContactsContract.Contacts.LOOKUP_KEY; | ||||
463 | | ||||
464 | public uID(String lookupKey) { | ||||
465 | contactLookupKey = lookupKey; | ||||
466 | } | ||||
467 | | ||||
468 | public String toString() { | ||||
469 | return this.contactLookupKey; | ||||
470 | } | ||||
471 | | ||||
472 | @Override | ||||
473 | public int hashCode() { | ||||
474 | return contactLookupKey.hashCode(); | ||||
475 | } | ||||
476 | | ||||
477 | @Override | ||||
478 | public boolean equals(Object other) { | ||||
479 | if (other instanceof uID) { | ||||
480 | return contactLookupKey.equals(((uID) other).contactLookupKey); | ||||
481 | } | ||||
482 | return contactLookupKey.equals(other); | ||||
483 | } | ||||
484 | } | ||||
485 | } |