1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/ContactService.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2015 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko; 1.9 + 1.10 +import java.util.ArrayList; 1.11 +import java.util.Collections; 1.12 +import java.util.Comparator; 1.13 +import java.util.HashMap; 1.14 +import java.util.List; 1.15 +import java.util.Map.Entry; 1.16 + 1.17 +import org.json.JSONArray; 1.18 +import org.json.JSONException; 1.19 +import org.json.JSONObject; 1.20 +import org.mozilla.gecko.util.GeckoEventListener; 1.21 +import org.mozilla.gecko.util.ThreadUtils; 1.22 + 1.23 +import android.accounts.Account; 1.24 +import android.accounts.AccountManager; 1.25 +import android.app.AlertDialog; 1.26 +import android.content.ContentProviderOperation; 1.27 +import android.content.ContentProviderResult; 1.28 +import android.content.ContentResolver; 1.29 +import android.content.ContentUris; 1.30 +import android.content.ContentValues; 1.31 +import android.content.DialogInterface; 1.32 +import android.content.OperationApplicationException; 1.33 +import android.database.Cursor; 1.34 +import android.net.Uri; 1.35 +import android.os.Build; 1.36 +import android.os.RemoteException; 1.37 +import android.provider.ContactsContract; 1.38 +import android.provider.ContactsContract.CommonDataKinds.BaseTypes; 1.39 +import android.provider.ContactsContract.CommonDataKinds.Email; 1.40 +import android.provider.ContactsContract.CommonDataKinds.Event; 1.41 +import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 1.42 +import android.provider.ContactsContract.CommonDataKinds.Im; 1.43 +import android.provider.ContactsContract.CommonDataKinds.Nickname; 1.44 +import android.provider.ContactsContract.CommonDataKinds.Note; 1.45 +import android.provider.ContactsContract.CommonDataKinds.Organization; 1.46 +import android.provider.ContactsContract.CommonDataKinds.Phone; 1.47 +import android.provider.ContactsContract.CommonDataKinds.StructuredName; 1.48 +import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 1.49 +import android.provider.ContactsContract.CommonDataKinds.Website; 1.50 +import android.provider.ContactsContract.Data; 1.51 +import android.provider.ContactsContract.Groups; 1.52 +import android.provider.ContactsContract.RawContacts; 1.53 +import android.provider.ContactsContract.RawContacts.Entity; 1.54 +import android.telephony.PhoneNumberUtils; 1.55 +import android.util.Log; 1.56 + 1.57 +public class ContactService implements GeckoEventListener { 1.58 + private static final String LOGTAG = "GeckoContactService"; 1.59 + private static final boolean DEBUG = false; 1.60 + 1.61 + private final static int GROUP_ACCOUNT_NAME = 0; 1.62 + private final static int GROUP_ACCOUNT_TYPE = 1; 1.63 + private final static int GROUP_ID = 2; 1.64 + private final static int GROUP_TITLE = 3; 1.65 + private final static int GROUP_AUTO_ADD = 4; 1.66 + 1.67 + private final static String CARRIER_COLUMN = Data.DATA5; 1.68 + private final static String CUSTOM_DATA_COLUMN = Data.DATA1; 1.69 + 1.70 + // Pre-Honeycomb versions of Android have a "My Contacts" system group that all contacts are 1.71 + // assigned to by default for a given account. After Honeycomb, an AUTO_ADD database column 1.72 + // was added to denote groups that contacts are automatically added to 1.73 + private final static String PRE_HONEYCOMB_DEFAULT_GROUP = "System Group: My Contacts"; 1.74 + private final static String MIMETYPE_ADDITIONAL_NAME = "org.mozilla.gecko/additional_name"; 1.75 + private final static String MIMETYPE_SEX = "org.mozilla.gecko/sex"; 1.76 + private final static String MIMETYPE_GENDER_IDENTITY = "org.mozilla.gecko/gender_identity"; 1.77 + private final static String MIMETYPE_KEY = "org.mozilla.gecko/key"; 1.78 + private final static String MIMETYPE_MOZILLA_CONTACTS_FLAG = "org.mozilla.gecko/contact_flag"; 1.79 + 1.80 + private final EventDispatcher mEventDispatcher; 1.81 + 1.82 + private String mAccountName; 1.83 + private String mAccountType; 1.84 + private String mGroupTitle; 1.85 + private long mGroupId; 1.86 + private boolean mGotDeviceAccount; 1.87 + 1.88 + private HashMap<String, String> mColumnNameConstantsMap; 1.89 + private HashMap<String, String> mMimeTypeConstantsMap; 1.90 + private HashMap<String, Integer> mAddressTypesMap; 1.91 + private HashMap<String, Integer> mPhoneTypesMap; 1.92 + private HashMap<String, Integer> mEmailTypesMap; 1.93 + private HashMap<String, Integer> mWebsiteTypesMap; 1.94 + private HashMap<String, Integer> mImTypesMap; 1.95 + 1.96 + private ContentResolver mContentResolver; 1.97 + private GeckoApp mActivity; 1.98 + 1.99 + ContactService(EventDispatcher eventDispatcher, GeckoApp activity) { 1.100 + mEventDispatcher = eventDispatcher; 1.101 + mActivity = activity; 1.102 + mContentResolver = mActivity.getContentResolver(); 1.103 + mGotDeviceAccount = false; 1.104 + 1.105 + registerEventListener("Android:Contacts:Clear"); 1.106 + registerEventListener("Android:Contacts:Find"); 1.107 + registerEventListener("Android:Contacts:GetAll"); 1.108 + registerEventListener("Android:Contacts:GetCount"); 1.109 + registerEventListener("Android:Contact:Remove"); 1.110 + registerEventListener("Android:Contact:Save"); 1.111 + } 1.112 + 1.113 + public void destroy() { 1.114 + unregisterEventListener("Android:Contacts:Clear"); 1.115 + unregisterEventListener("Android:Contacts:Find"); 1.116 + unregisterEventListener("Android:Contacts:GetAll"); 1.117 + unregisterEventListener("Android:Contacts:GetCount"); 1.118 + unregisterEventListener("Android:Contact:Remove"); 1.119 + unregisterEventListener("Android:Contact:Save"); 1.120 + } 1.121 + 1.122 + @Override 1.123 + public void handleMessage(final String event, final JSONObject message) { 1.124 + // If the account chooser dialog needs shown to the user, the message handling becomes 1.125 + // asychronous so it needs posted to a background thread from the UI thread when the 1.126 + // account chooser dialog is dismissed by the user. 1.127 + Runnable handleMessage = new Runnable() { 1.128 + @Override 1.129 + public void run() { 1.130 + try { 1.131 + if (DEBUG) { 1.132 + Log.d(LOGTAG, "Event: " + event + "\nMessage: " + message.toString(3)); 1.133 + } 1.134 + 1.135 + final JSONObject messageData = message.getJSONObject("data"); 1.136 + final String requestID = messageData.getString("requestID"); 1.137 + 1.138 + // Options may not exist for all operations 1.139 + JSONObject contactOptions = messageData.optJSONObject("options"); 1.140 + 1.141 + if ("Android:Contacts:Find".equals(event)) { 1.142 + findContacts(contactOptions, requestID); 1.143 + } else if ("Android:Contacts:GetAll".equals(event)) { 1.144 + getAllContacts(messageData, requestID); 1.145 + } else if ("Android:Contacts:Clear".equals(event)) { 1.146 + clearAllContacts(contactOptions, requestID); 1.147 + } else if ("Android:Contact:Save".equals(event)) { 1.148 + saveContact(contactOptions, requestID); 1.149 + } else if ("Android:Contact:Remove".equals(event)) { 1.150 + removeContact(contactOptions, requestID); 1.151 + } else if ("Android:Contacts:GetCount".equals(event)) { 1.152 + getContactsCount(requestID); 1.153 + } else { 1.154 + throw new IllegalArgumentException("Unexpected event: " + event); 1.155 + } 1.156 + } catch (JSONException e) { 1.157 + throw new IllegalArgumentException("Message: " + e); 1.158 + } 1.159 + } 1.160 + }; 1.161 + 1.162 + // Get the account name/type if they haven't been set yet 1.163 + if (!mGotDeviceAccount) { 1.164 + getDeviceAccount(handleMessage); 1.165 + } else { 1.166 + handleMessage.run(); 1.167 + } 1.168 + } 1.169 + 1.170 + private void findContacts(final JSONObject contactOptions, final String requestID) { 1.171 + long[] rawContactIds = findContactsRawIds(contactOptions); 1.172 + Log.i(LOGTAG, "Got " + (rawContactIds != null ? rawContactIds.length : "null") + " raw contact IDs"); 1.173 + 1.174 + final String[] sortOptions = getSortOptionsFromJSON(contactOptions); 1.175 + 1.176 + if (rawContactIds == null || sortOptions == null) { 1.177 + sendCallbackToJavascript("Android:Contacts:Find:Return:KO", requestID, null, null); 1.178 + } else { 1.179 + sendCallbackToJavascript("Android:Contacts:Find:Return:OK", requestID, 1.180 + new String[] {"contacts"}, 1.181 + new Object[] {getContactsAsJSONArray(rawContactIds, sortOptions[0], 1.182 + sortOptions[1])}); 1.183 + } 1.184 + } 1.185 + 1.186 + private void getAllContacts(final JSONObject contactOptions, final String requestID) { 1.187 + long[] rawContactIds = getAllRawContactIds(); 1.188 + Log.i(LOGTAG, "Got " + rawContactIds.length + " raw contact IDs"); 1.189 + 1.190 + final String[] sortOptions = getSortOptionsFromJSON(contactOptions); 1.191 + 1.192 + if (rawContactIds == null || sortOptions == null) { 1.193 + // There's no failure message for getAll 1.194 + return; 1.195 + } else { 1.196 + sendCallbackToJavascript("Android:Contacts:GetAll:Next", requestID, 1.197 + new String[] {"contacts"}, 1.198 + new Object[] {getContactsAsJSONArray(rawContactIds, sortOptions[0], 1.199 + sortOptions[1])}); 1.200 + } 1.201 + } 1.202 + 1.203 + private static String[] getSortOptionsFromJSON(final JSONObject contactOptions) { 1.204 + String sortBy = null; 1.205 + String sortOrder = null; 1.206 + 1.207 + try { 1.208 + final JSONObject findOptions = contactOptions.getJSONObject("findOptions"); 1.209 + sortBy = findOptions.optString("sortBy").toLowerCase(); 1.210 + sortOrder = findOptions.optString("sortOrder").toLowerCase(); 1.211 + 1.212 + if ("".equals(sortBy)) { 1.213 + sortBy = null; 1.214 + } 1.215 + if ("".equals(sortOrder)) { 1.216 + sortOrder = "ascending"; 1.217 + } 1.218 + 1.219 + // Only "familyname" and "givenname" are valid sortBy values and only "ascending" 1.220 + // and "descending" are valid sortOrder values 1.221 + if ((sortBy != null && !"familyname".equals(sortBy) && !"givenname".equals(sortBy)) || 1.222 + (!"ascending".equals(sortOrder) && !"descending".equals(sortOrder))) { 1.223 + return null; 1.224 + } 1.225 + } catch (JSONException e) { 1.226 + throw new IllegalArgumentException(e); 1.227 + } 1.228 + 1.229 + return new String[] {sortBy, sortOrder}; 1.230 + } 1.231 + 1.232 + private long[] findContactsRawIds(final JSONObject contactOptions) { 1.233 + List<Long> rawContactIds = new ArrayList<Long>(); 1.234 + Cursor cursor = null; 1.235 + 1.236 + try { 1.237 + final JSONObject findOptions = contactOptions.getJSONObject("findOptions"); 1.238 + String filterValue = findOptions.optString("filterValue"); 1.239 + JSONArray filterBy = findOptions.optJSONArray("filterBy"); 1.240 + final String filterOp = findOptions.optString("filterOp"); 1.241 + final int filterLimit = findOptions.getInt("filterLimit"); 1.242 + final int substringMatching = findOptions.getInt("substringMatching"); 1.243 + 1.244 + // If filter value is undefined, avoid all the logic below and just return 1.245 + // all available raw contact IDs 1.246 + if ("".equals(filterValue) || "".equals(filterOp)) { 1.247 + long[] allRawContactIds = getAllRawContactIds(); 1.248 + 1.249 + // Truncate the raw contacts IDs array if necessary 1.250 + if (filterLimit > 0 && allRawContactIds.length > filterLimit) { 1.251 + long[] truncatedRawContactIds = new long[filterLimit]; 1.252 + for (int i = 0; i < filterLimit; i++) { 1.253 + truncatedRawContactIds[i] = allRawContactIds[i]; 1.254 + } 1.255 + return truncatedRawContactIds; 1.256 + } 1.257 + return allRawContactIds; 1.258 + } 1.259 + 1.260 + // "match" can only be used with the "tel" field 1.261 + if ("match".equals(filterOp)) { 1.262 + for (int i = 0; i < filterBy.length(); i++) { 1.263 + if (!"tel".equals(filterBy.getString(i))) { 1.264 + Log.w(LOGTAG, "\"match\" filterBy option is only valid for the \"tel\" field"); 1.265 + return null; 1.266 + } 1.267 + } 1.268 + } 1.269 + 1.270 + // Only select contacts from the selected account 1.271 + String selection = null; 1.272 + String[] selectionArgs = null; 1.273 + 1.274 + if (mAccountName != null) { 1.275 + selection = RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?"; 1.276 + selectionArgs = new String[] {mAccountName, mAccountType}; 1.277 + } 1.278 + 1.279 + 1.280 + final String[] columnsToGet; 1.281 + 1.282 + // If a filterBy value was not specified, search all columns 1.283 + if (filterBy == null || filterBy.length() == 0) { 1.284 + columnsToGet = null; 1.285 + } else { 1.286 + // Only get the columns given in the filterBy array 1.287 + List<String> columnsToGetList = new ArrayList<String>(); 1.288 + 1.289 + columnsToGetList.add(Data.RAW_CONTACT_ID); 1.290 + columnsToGetList.add(Data.MIMETYPE); 1.291 + for (int i = 0; i < filterBy.length(); i++) { 1.292 + final String field = filterBy.getString(i); 1.293 + 1.294 + // If one of the filterBy fields is the ID, just return the filter value 1.295 + // which should be the ID 1.296 + if ("id".equals(field)) { 1.297 + try { 1.298 + return new long[] {Long.valueOf(filterValue)}; 1.299 + } catch (NumberFormatException e) { 1.300 + // If the ID couldn't be converted to a long, it's invalid data 1.301 + // so return null for failure 1.302 + return null; 1.303 + } 1.304 + } 1.305 + 1.306 + final String columnName = getColumnNameConstant(field); 1.307 + 1.308 + if (columnName != null) { 1.309 + columnsToGetList.add(columnName); 1.310 + } else { 1.311 + Log.w(LOGTAG, "Unknown filter option: " + field); 1.312 + } 1.313 + } 1.314 + 1.315 + columnsToGet = columnsToGetList.toArray(new String[columnsToGetList.size()]); 1.316 + } 1.317 + 1.318 + // Execute the query 1.319 + cursor = mContentResolver.query(Data.CONTENT_URI, columnsToGet, selection, 1.320 + selectionArgs, null); 1.321 + 1.322 + if (cursor.getCount() > 0) { 1.323 + cursor.moveToPosition(-1); 1.324 + while (cursor.moveToNext()) { 1.325 + String mimeType = cursor.getString(cursor.getColumnIndex(Data.MIMETYPE)); 1.326 + 1.327 + // Check if the current mimetype is one of the types to filter by 1.328 + if (filterBy != null && filterBy.length() > 0) { 1.329 + for (int i = 0; i < filterBy.length(); i++) { 1.330 + String currentFilterBy = filterBy.getString(i); 1.331 + 1.332 + if (mimeType.equals(getMimeTypeOfField(currentFilterBy))) { 1.333 + String columnName = getColumnNameConstant(currentFilterBy); 1.334 + int columnIndex = cursor.getColumnIndex(columnName); 1.335 + String databaseValue = cursor.getString(columnIndex); 1.336 + 1.337 + boolean isPhone = false; 1.338 + if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.339 + isPhone = true; 1.340 + } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.341 + // Translate the group ID to the group name for matching 1.342 + try { 1.343 + databaseValue = getGroupName(Long.valueOf(databaseValue)); 1.344 + } catch (NumberFormatException e) { 1.345 + Log.e(LOGTAG, "Number Format Exception", e); 1.346 + continue; 1.347 + } 1.348 + } else if (databaseValue == null) { 1.349 + continue; 1.350 + } 1.351 + 1.352 + // Check if the value matches the filter value 1.353 + if (isFindMatch(filterOp, filterValue, databaseValue, isPhone, substringMatching)) { 1.354 + addMatchToList(cursor, rawContactIds); 1.355 + break; 1.356 + } 1.357 + } 1.358 + } 1.359 + } else { 1.360 + // If no filterBy options were given, check each column for a match 1.361 + int numColumns = cursor.getColumnCount(); 1.362 + for (int i = 0; i < numColumns; i++) { 1.363 + String databaseValue = cursor.getString(i); 1.364 + if (databaseValue != null && isFindMatch(filterOp, filterValue, databaseValue, false, substringMatching)) { 1.365 + addMatchToList(cursor, rawContactIds); 1.366 + break; 1.367 + } 1.368 + } 1.369 + } 1.370 + 1.371 + // If the max found contacts size has been hit, stop looking for contacts 1.372 + // A filter limit of 0 denotes there is no limit 1.373 + if (filterLimit > 0 && filterLimit <= rawContactIds.size()) { 1.374 + break; 1.375 + } 1.376 + } 1.377 + } 1.378 + } catch (JSONException e) { 1.379 + throw new IllegalArgumentException(e); 1.380 + } finally { 1.381 + if (cursor != null) { 1.382 + cursor.close(); 1.383 + } 1.384 + } 1.385 + 1.386 + // Return the contact IDs list converted to an array 1.387 + return convertLongListToArray(rawContactIds); 1.388 + } 1.389 + 1.390 + private boolean isFindMatch(final String filterOp, String filterValue, String databaseValue, 1.391 + final boolean isPhone, final int substringMatching) { 1.392 + Log.i(LOGTAG, "matching: filterOp: " + filterOp); 1.393 + if (DEBUG) { 1.394 + Log.d(LOGTAG, "matching: filterValue: " + filterValue); 1.395 + Log.d(LOGTAG, "matching: databaseValue: " + databaseValue); 1.396 + } 1.397 + Log.i(LOGTAG, "matching: isPhone: " + isPhone); 1.398 + Log.i(LOGTAG, "matching: substringMatching: " + substringMatching); 1.399 + 1.400 + if (databaseValue == null) { 1.401 + return false; 1.402 + } 1.403 + 1.404 + filterValue = filterValue.toLowerCase(); 1.405 + databaseValue = databaseValue.toLowerCase(); 1.406 + 1.407 + if ("match".equals(filterOp)) { 1.408 + // If substring matching is a positive number, only pay attention to the last X characters 1.409 + // of both the filter and database values 1.410 + if (substringMatching > 0) { 1.411 + databaseValue = substringStartFromEnd(cleanPhoneNumber(databaseValue), substringMatching); 1.412 + filterValue = substringStartFromEnd(cleanPhoneNumber(filterValue), substringMatching); 1.413 + return databaseValue.startsWith(filterValue); 1.414 + } 1.415 + return databaseValue.equals(filterValue); 1.416 + } else if ("equals".equals(filterOp)) { 1.417 + if (isPhone) { 1.418 + return PhoneNumberUtils.compare(filterValue, databaseValue); 1.419 + } 1.420 + return databaseValue.equals(filterValue); 1.421 + } else if ("contains".equals(filterOp)) { 1.422 + if (isPhone) { 1.423 + filterValue = cleanPhoneNumber(filterValue); 1.424 + databaseValue = cleanPhoneNumber(databaseValue); 1.425 + } 1.426 + return databaseValue.contains(filterValue); 1.427 + } else if ("startsWith".equals(filterOp)) { 1.428 + // If a phone number, remove non-dialable characters and then only pay attention to 1.429 + // the last X digits given by the substring matching values (see bug 877302) 1.430 + if (isPhone) { 1.431 + String cleanedDatabasePhone = cleanPhoneNumber(databaseValue); 1.432 + if (substringMatching > 0) { 1.433 + cleanedDatabasePhone = substringStartFromEnd(cleanedDatabasePhone, substringMatching); 1.434 + } 1.435 + 1.436 + if (cleanedDatabasePhone.startsWith(filterValue)) { 1.437 + return true; 1.438 + } 1.439 + } 1.440 + return databaseValue.startsWith(filterValue); 1.441 + } 1.442 + return false; 1.443 + } 1.444 + 1.445 + private static String cleanPhoneNumber(String phone) { 1.446 + return phone.replace(" ", "").replace("(", "").replace(")", "").replace("-", ""); 1.447 + } 1.448 + 1.449 + private static String substringStartFromEnd(final String string, final int distanceFromEnd) { 1.450 + int stringLen = string.length(); 1.451 + if (stringLen < distanceFromEnd) { 1.452 + return string; 1.453 + } 1.454 + return string.substring(stringLen - distanceFromEnd); 1.455 + } 1.456 + 1.457 + private static void addMatchToList(final Cursor cursor, List<Long> rawContactIds) { 1.458 + long rawContactId = cursor.getLong(cursor.getColumnIndex(Data.RAW_CONTACT_ID)); 1.459 + if (!rawContactIds.contains(rawContactId)) { 1.460 + rawContactIds.add(rawContactId); 1.461 + } 1.462 + } 1.463 + 1.464 + private JSONArray getContactsAsJSONArray(final long[] rawContactIds, final String sortBy, final String sortOrder) { 1.465 + List<JSONObject> contactsList = new ArrayList<JSONObject>(); 1.466 + JSONArray contactsArray = new JSONArray(); 1.467 + 1.468 + // Get each contact as a JSON object 1.469 + for (int i = 0; i < rawContactIds.length; i++) { 1.470 + contactsList.add(getContactAsJSONObject(rawContactIds[i])); 1.471 + } 1.472 + 1.473 + // Sort the contacts 1.474 + if (sortBy != null) { 1.475 + Collections.sort(contactsList, new ContactsComparator(sortBy, sortOrder)); 1.476 + } 1.477 + 1.478 + // Convert the contacts list to a JSON array 1.479 + for (int i = 0; i < contactsList.size(); i++) { 1.480 + contactsArray.put(contactsList.get(i)); 1.481 + } 1.482 + 1.483 + return contactsArray; 1.484 + } 1.485 + 1.486 + private JSONObject getContactAsJSONObject(long rawContactId) { 1.487 + // ContactManager wants a contact object with it's properties wrapped in an array of objects 1.488 + JSONObject contact = new JSONObject(); 1.489 + JSONObject contactProperties = new JSONObject(); 1.490 + 1.491 + JSONArray names = new JSONArray(); 1.492 + JSONArray givenNames = new JSONArray(); 1.493 + JSONArray familyNames = new JSONArray(); 1.494 + JSONArray honorificPrefixes = new JSONArray(); 1.495 + JSONArray honorificSuffixes = new JSONArray(); 1.496 + JSONArray additionalNames = new JSONArray(); 1.497 + JSONArray nicknames = new JSONArray(); 1.498 + JSONArray addresses = new JSONArray(); 1.499 + JSONArray phones = new JSONArray(); 1.500 + JSONArray emails = new JSONArray(); 1.501 + JSONArray organizations = new JSONArray(); 1.502 + JSONArray jobTitles = new JSONArray(); 1.503 + JSONArray notes = new JSONArray(); 1.504 + JSONArray urls = new JSONArray(); 1.505 + JSONArray impps = new JSONArray(); 1.506 + JSONArray categories = new JSONArray(); 1.507 + String bday = null; 1.508 + String anniversary = null; 1.509 + String sex = null; 1.510 + String genderIdentity = null; 1.511 + JSONArray key = new JSONArray(); 1.512 + 1.513 + // Get all the data columns 1.514 + final String[] columnsToGet = getAllColumns(); 1.515 + 1.516 + Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); 1.517 + Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY); 1.518 + 1.519 + Cursor cursor = mContentResolver.query(entityUri, columnsToGet, null, null, null); 1.520 + cursor.moveToPosition(-1); 1.521 + while (cursor.moveToNext()) { 1.522 + String mimeType = cursor.getString(cursor.getColumnIndex(Data.MIMETYPE)); 1.523 + 1.524 + // Put the proper fields for each mimetype into the JSON arrays 1.525 + try { 1.526 + if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.527 + final String displayName = cursor.getString(cursor.getColumnIndex(StructuredName.DISPLAY_NAME)); 1.528 + final String givenName = cursor.getString(cursor.getColumnIndex(StructuredName.GIVEN_NAME)); 1.529 + final String familyName = cursor.getString(cursor.getColumnIndex(StructuredName.FAMILY_NAME)); 1.530 + final String prefix = cursor.getString(cursor.getColumnIndex(StructuredName.PREFIX)); 1.531 + final String suffix = cursor.getString(cursor.getColumnIndex(StructuredName.SUFFIX)); 1.532 + 1.533 + if (displayName != null) { 1.534 + names.put(displayName); 1.535 + } 1.536 + if (givenName != null) { 1.537 + givenNames.put(givenName); 1.538 + } 1.539 + if (familyName != null) { 1.540 + familyNames.put(familyName); 1.541 + } 1.542 + if (prefix != null) { 1.543 + honorificPrefixes.put(prefix); 1.544 + } 1.545 + if (suffix != null) { 1.546 + honorificSuffixes.put(suffix); 1.547 + } 1.548 + 1.549 + } else if (MIMETYPE_ADDITIONAL_NAME.equals(mimeType)) { 1.550 + additionalNames.put(cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN))); 1.551 + 1.552 + } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.553 + nicknames.put(cursor.getString(cursor.getColumnIndex(Nickname.NAME))); 1.554 + 1.555 + } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.556 + initAddressTypesMap(); 1.557 + getAddressDataAsJSONObject(cursor, addresses); 1.558 + 1.559 + } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.560 + initPhoneTypesMap(); 1.561 + getPhoneDataAsJSONObject(cursor, phones); 1.562 + 1.563 + } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.564 + initEmailTypesMap(); 1.565 + getGenericDataAsJSONObject(cursor, emails, Email.ADDRESS, Email.TYPE, Email.LABEL, mEmailTypesMap); 1.566 + 1.567 + } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.568 + getOrganizationDataAsJSONObject(cursor, organizations, jobTitles); 1.569 + 1.570 + } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.571 + notes.put(cursor.getString(cursor.getColumnIndex(Note.NOTE))); 1.572 + 1.573 + } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.574 + initWebsiteTypesMap(); 1.575 + getGenericDataAsJSONObject(cursor, urls, Website.URL, Website.TYPE, Website.LABEL, mWebsiteTypesMap); 1.576 + 1.577 + } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.578 + initImTypesMap(); 1.579 + getGenericDataAsJSONObject(cursor, impps, Im.DATA, Im.TYPE, Im.LABEL, mImTypesMap); 1.580 + 1.581 + } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.582 + long groupId = cursor.getLong(cursor.getColumnIndex(GroupMembership.GROUP_ROW_ID)); 1.583 + String groupName = getGroupName(groupId); 1.584 + if (!doesJSONArrayContainString(categories, groupName)) { 1.585 + categories.put(groupName); 1.586 + } 1.587 + 1.588 + } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) { 1.589 + int type = cursor.getInt(cursor.getColumnIndex(Event.TYPE)); 1.590 + String date = cursor.getString(cursor.getColumnIndex(Event.START_DATE)); 1.591 + 1.592 + // Add the time info onto the date so it correctly parses into a JS date object 1.593 + date += "T00:00:00"; 1.594 + 1.595 + switch (type) { 1.596 + case Event.TYPE_BIRTHDAY: 1.597 + bday = date; 1.598 + break; 1.599 + 1.600 + case Event.TYPE_ANNIVERSARY: 1.601 + anniversary = date; 1.602 + break; 1.603 + } 1.604 + 1.605 + } else if (MIMETYPE_SEX.equals(mimeType)) { 1.606 + sex = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)); 1.607 + 1.608 + } else if (MIMETYPE_GENDER_IDENTITY.equals(mimeType)) { 1.609 + genderIdentity = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)); 1.610 + 1.611 + } else if (MIMETYPE_KEY.equals(mimeType)) { 1.612 + key.put(cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN))); 1.613 + } 1.614 + } catch (JSONException e) { 1.615 + throw new IllegalArgumentException(e); 1.616 + } 1.617 + } 1.618 + cursor.close(); 1.619 + 1.620 + try { 1.621 + // Add the fields to the contact properties object 1.622 + contactProperties.put("name", names); 1.623 + contactProperties.put("givenName", givenNames); 1.624 + contactProperties.put("familyName", familyNames); 1.625 + contactProperties.put("honorificPrefix", honorificPrefixes); 1.626 + contactProperties.put("honorificSuffix", honorificSuffixes); 1.627 + contactProperties.put("additionalName", additionalNames); 1.628 + contactProperties.put("nickname", nicknames); 1.629 + contactProperties.put("adr", addresses); 1.630 + contactProperties.put("tel", phones); 1.631 + contactProperties.put("email", emails); 1.632 + contactProperties.put("org", organizations); 1.633 + contactProperties.put("jobTitle", jobTitles); 1.634 + contactProperties.put("note", notes); 1.635 + contactProperties.put("url", urls); 1.636 + contactProperties.put("impp", impps); 1.637 + contactProperties.put("category", categories); 1.638 + contactProperties.put("key", key); 1.639 + 1.640 + putPossibleNullValueInJSONObject("bday", bday, contactProperties); 1.641 + putPossibleNullValueInJSONObject("anniversary", anniversary, contactProperties); 1.642 + putPossibleNullValueInJSONObject("sex", sex, contactProperties); 1.643 + putPossibleNullValueInJSONObject("genderIdentity", genderIdentity, contactProperties); 1.644 + 1.645 + // Add the raw contact ID and the properties to the contact 1.646 + contact.put("id", String.valueOf(rawContactId)); 1.647 + contact.put("updated", null); 1.648 + contact.put("published", null); 1.649 + contact.put("properties", contactProperties); 1.650 + } catch (JSONException e) { 1.651 + throw new IllegalArgumentException(e); 1.652 + } 1.653 + 1.654 + if (DEBUG) { 1.655 + try { 1.656 + Log.d(LOGTAG, "Got contact: " + contact.toString(3)); 1.657 + } catch (JSONException e) {} 1.658 + } 1.659 + 1.660 + return contact; 1.661 + } 1.662 + 1.663 + private boolean bool(int integer) { 1.664 + return integer != 0 ? true : false; 1.665 + } 1.666 + 1.667 + private void getGenericDataAsJSONObject(Cursor cursor, JSONArray array, final String dataColumn, 1.668 + final String typeColumn, final String typeLabelColumn, 1.669 + final HashMap<String, Integer> typeMap) throws JSONException { 1.670 + String value = cursor.getString(cursor.getColumnIndex(dataColumn)); 1.671 + int typeConstant = cursor.getInt(cursor.getColumnIndex(typeColumn)); 1.672 + String type; 1.673 + if (typeConstant == BaseTypes.TYPE_CUSTOM) { 1.674 + type = cursor.getString(cursor.getColumnIndex(typeLabelColumn)); 1.675 + } else { 1.676 + type = getKeyFromMapValue(typeMap, Integer.valueOf(typeConstant)); 1.677 + } 1.678 + 1.679 + // Since an object may have multiple types, it may have already been added, 1.680 + // but still needs the new type added 1.681 + boolean found = false; 1.682 + if (type != null) { 1.683 + for (int i = 0; i < array.length(); i++) { 1.684 + JSONObject object = array.getJSONObject(i); 1.685 + if (value.equals(object.getString("value"))) { 1.686 + found = true; 1.687 + 1.688 + JSONArray types = object.getJSONArray("type"); 1.689 + if (!doesJSONArrayContainString(types, type)) { 1.690 + types.put(type); 1.691 + break; 1.692 + } 1.693 + } 1.694 + } 1.695 + } 1.696 + 1.697 + // If an existing object wasn't found, make a new one 1.698 + if (!found) { 1.699 + JSONObject object = new JSONObject(); 1.700 + JSONArray types = new JSONArray(); 1.701 + object.put("value", value); 1.702 + types.put(type); 1.703 + object.put("type", types); 1.704 + object.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Data.IS_SUPER_PRIMARY)))); 1.705 + 1.706 + array.put(object); 1.707 + } 1.708 + } 1.709 + 1.710 + private void getPhoneDataAsJSONObject(Cursor cursor, JSONArray phones) throws JSONException { 1.711 + String value = cursor.getString(cursor.getColumnIndex(Phone.NUMBER)); 1.712 + int typeConstant = cursor.getInt(cursor.getColumnIndex(Phone.TYPE)); 1.713 + String type; 1.714 + if (typeConstant == Phone.TYPE_CUSTOM) { 1.715 + type = cursor.getString(cursor.getColumnIndex(Phone.LABEL)); 1.716 + } else { 1.717 + type = getKeyFromMapValue(mPhoneTypesMap, Integer.valueOf(typeConstant)); 1.718 + } 1.719 + 1.720 + // Since a phone may have multiple types, it may have already been added, 1.721 + // but still needs the new type added 1.722 + boolean found = false; 1.723 + if (type != null) { 1.724 + for (int i = 0; i < phones.length(); i++) { 1.725 + JSONObject phone = phones.getJSONObject(i); 1.726 + if (value.equals(phone.getString("value"))) { 1.727 + found = true; 1.728 + 1.729 + JSONArray types = phone.getJSONArray("type"); 1.730 + if (!doesJSONArrayContainString(types, type)) { 1.731 + types.put(type); 1.732 + break; 1.733 + } 1.734 + } 1.735 + } 1.736 + } 1.737 + 1.738 + // If an existing phone wasn't found, make a new one 1.739 + if (!found) { 1.740 + JSONObject phone = new JSONObject(); 1.741 + JSONArray types = new JSONArray(); 1.742 + phone.put("value", value); 1.743 + phone.put("type", type); 1.744 + types.put(type); 1.745 + phone.put("type", types); 1.746 + phone.put("carrier", cursor.getString(cursor.getColumnIndex(CARRIER_COLUMN))); 1.747 + phone.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Phone.IS_SUPER_PRIMARY)))); 1.748 + 1.749 + phones.put(phone); 1.750 + } 1.751 + } 1.752 + 1.753 + private void getAddressDataAsJSONObject(Cursor cursor, JSONArray addresses) throws JSONException { 1.754 + String streetAddress = cursor.getString(cursor.getColumnIndex(StructuredPostal.STREET)); 1.755 + String locality = cursor.getString(cursor.getColumnIndex(StructuredPostal.CITY)); 1.756 + String region = cursor.getString(cursor.getColumnIndex(StructuredPostal.REGION)); 1.757 + String postalCode = cursor.getString(cursor.getColumnIndex(StructuredPostal.POSTCODE)); 1.758 + String countryName = cursor.getString(cursor.getColumnIndex(StructuredPostal.COUNTRY)); 1.759 + int typeConstant = cursor.getInt(cursor.getColumnIndex(StructuredPostal.TYPE)); 1.760 + String type; 1.761 + if (typeConstant == StructuredPostal.TYPE_CUSTOM) { 1.762 + type = cursor.getString(cursor.getColumnIndex(StructuredPostal.LABEL)); 1.763 + } else { 1.764 + type = getKeyFromMapValue(mAddressTypesMap, Integer.valueOf(typeConstant)); 1.765 + } 1.766 + 1.767 + // Since an email may have multiple types, it may have already been added, 1.768 + // but still needs the new type added 1.769 + boolean found = false; 1.770 + if (type != null) { 1.771 + for (int i = 0; i < addresses.length(); i++) { 1.772 + JSONObject address = addresses.getJSONObject(i); 1.773 + if (streetAddress.equals(address.getString("streetAddress")) && 1.774 + locality.equals(address.getString("locality")) && 1.775 + region.equals(address.getString("region")) && 1.776 + countryName.equals(address.getString("countryName")) && 1.777 + postalCode.equals(address.getString("postalCode"))) { 1.778 + found = true; 1.779 + 1.780 + JSONArray types = address.getJSONArray("type"); 1.781 + if (!doesJSONArrayContainString(types, type)) { 1.782 + types.put(type); 1.783 + break; 1.784 + } 1.785 + } 1.786 + } 1.787 + } 1.788 + 1.789 + // If an existing email wasn't found, make a new one 1.790 + if (!found) { 1.791 + JSONObject address = new JSONObject(); 1.792 + JSONArray types = new JSONArray(); 1.793 + address.put("streetAddress", streetAddress); 1.794 + address.put("locality", locality); 1.795 + address.put("region", region); 1.796 + address.put("countryName", countryName); 1.797 + address.put("postalCode", postalCode); 1.798 + types.put(type); 1.799 + address.put("type", types); 1.800 + address.put("pref", bool(cursor.getInt(cursor.getColumnIndex(StructuredPostal.IS_SUPER_PRIMARY)))); 1.801 + 1.802 + addresses.put(address); 1.803 + } 1.804 + } 1.805 + 1.806 + private void getOrganizationDataAsJSONObject(Cursor cursor, JSONArray organizations, 1.807 + JSONArray jobTitles) throws JSONException { 1.808 + int organizationColumnIndex = cursor.getColumnIndex(Organization.COMPANY); 1.809 + int titleColumnIndex = cursor.getColumnIndex(Organization.TITLE); 1.810 + 1.811 + if (!cursor.isNull(organizationColumnIndex)) { 1.812 + organizations.put(cursor.getString(organizationColumnIndex)); 1.813 + } 1.814 + if (!cursor.isNull(titleColumnIndex)) { 1.815 + jobTitles.put(cursor.getString(titleColumnIndex)); 1.816 + } 1.817 + } 1.818 + 1.819 + private class ContactsComparator implements Comparator<JSONObject> { 1.820 + final String mSortBy; 1.821 + final String mSortOrder; 1.822 + 1.823 + public ContactsComparator(final String sortBy, final String sortOrder) { 1.824 + mSortBy = sortBy.toLowerCase(); 1.825 + mSortOrder = sortOrder.toLowerCase(); 1.826 + } 1.827 + 1.828 + @Override 1.829 + public int compare(JSONObject left, JSONObject right) { 1.830 + // Determine if sorting by "family name, given name" or "given name, family name" 1.831 + boolean familyFirst = false; 1.832 + if ("familyname".equals(mSortBy)) { 1.833 + familyFirst = true; 1.834 + } 1.835 + 1.836 + JSONObject leftProperties; 1.837 + JSONObject rightProperties; 1.838 + try { 1.839 + leftProperties = left.getJSONObject("properties"); 1.840 + rightProperties = right.getJSONObject("properties"); 1.841 + } catch (JSONException e) { 1.842 + throw new IllegalArgumentException(e); 1.843 + } 1.844 + 1.845 + JSONArray leftFamilyNames = leftProperties.optJSONArray("familyName"); 1.846 + JSONArray leftGivenNames = leftProperties.optJSONArray("givenName"); 1.847 + JSONArray rightFamilyNames = rightProperties.optJSONArray("familyName"); 1.848 + JSONArray rightGivenNames = rightProperties.optJSONArray("givenName"); 1.849 + 1.850 + // If any of the name arrays didn't exist (are null), create empty arrays 1.851 + // to avoid doing a bunch of null checking below 1.852 + if (leftFamilyNames == null) { 1.853 + leftFamilyNames = new JSONArray(); 1.854 + } 1.855 + if (leftGivenNames == null) { 1.856 + leftGivenNames = new JSONArray(); 1.857 + } 1.858 + if (rightFamilyNames == null) { 1.859 + rightFamilyNames = new JSONArray(); 1.860 + } 1.861 + if (rightGivenNames == null) { 1.862 + rightGivenNames = new JSONArray(); 1.863 + } 1.864 + 1.865 + int maxArrayLength = max(leftFamilyNames.length(), leftGivenNames.length(), 1.866 + rightFamilyNames.length(), rightGivenNames.length()); 1.867 + 1.868 + int index = 0; 1.869 + int compareResult; 1.870 + do { 1.871 + // Join together the given name and family name per the pattern above 1.872 + String leftName = ""; 1.873 + String rightName = ""; 1.874 + 1.875 + if (familyFirst) { 1.876 + leftName = leftFamilyNames.optString(index, "") + leftGivenNames.optString(index, ""); 1.877 + rightName = rightFamilyNames.optString(index, "") + rightGivenNames.optString(index, ""); 1.878 + } else { 1.879 + leftName = leftGivenNames.optString(index, "") + leftFamilyNames.optString(index, ""); 1.880 + rightName = rightGivenNames.optString(index, "") + rightFamilyNames.optString(index, ""); 1.881 + } 1.882 + 1.883 + index++; 1.884 + compareResult = leftName.compareTo(rightName); 1.885 + 1.886 + } while (compareResult == 0 && index < maxArrayLength); 1.887 + 1.888 + // If descending order, flip the result 1.889 + if (compareResult != 0 && "descending".equals(mSortOrder)) { 1.890 + compareResult = -compareResult; 1.891 + } 1.892 + 1.893 + return compareResult; 1.894 + } 1.895 + } 1.896 + 1.897 + private void clearAllContacts(final JSONObject contactOptions, final String requestID) { 1.898 + ArrayList<ContentProviderOperation> deleteOptions = new ArrayList<ContentProviderOperation>(); 1.899 + 1.900 + // Delete all contacts from the selected account 1.901 + ContentProviderOperation.Builder deleteOptionsBuilder = ContentProviderOperation.newDelete(RawContacts.CONTENT_URI); 1.902 + if (mAccountName != null) { 1.903 + deleteOptionsBuilder.withSelection(RawContacts.ACCOUNT_NAME + "=?", new String[] {mAccountName}) 1.904 + .withSelection(RawContacts.ACCOUNT_TYPE + "=?", new String[] {mAccountType}); 1.905 + } 1.906 + 1.907 + deleteOptions.add(deleteOptionsBuilder.build()); 1.908 + 1.909 + // Clear the contacts 1.910 + String returnStatus = "KO"; 1.911 + if (applyBatch(deleteOptions) != null) { 1.912 + returnStatus = "OK"; 1.913 + } 1.914 + 1.915 + Log.i(LOGTAG, "Sending return status: " + returnStatus); 1.916 + 1.917 + sendCallbackToJavascript("Android:Contacts:Clear:Return:" + returnStatus, requestID, 1.918 + new String[] {"contactID"}, new Object[] {"undefined"}); 1.919 + 1.920 + } 1.921 + 1.922 + private boolean deleteContact(String rawContactId) { 1.923 + ContentProviderOperation deleteOptions = ContentProviderOperation.newDelete(RawContacts.CONTENT_URI) 1.924 + .withSelection(RawContacts._ID + "=?", 1.925 + new String[] {rawContactId}) 1.926 + .build(); 1.927 + 1.928 + ArrayList<ContentProviderOperation> deleteOptionsList = new ArrayList<ContentProviderOperation>(); 1.929 + deleteOptionsList.add(deleteOptions); 1.930 + 1.931 + return checkForPositiveCountInResults(applyBatch(deleteOptionsList)); 1.932 + } 1.933 + 1.934 + private void removeContact(final JSONObject contactOptions, final String requestID) { 1.935 + String rawContactId; 1.936 + try { 1.937 + rawContactId = contactOptions.getString("id"); 1.938 + Log.i(LOGTAG, "Removing contact with ID: " + rawContactId); 1.939 + } catch (JSONException e) { 1.940 + // We can't continue without a raw contact ID 1.941 + sendCallbackToJavascript("Android:Contact:Remove:Return:KO", requestID, null, null); 1.942 + return; 1.943 + } 1.944 + 1.945 + String returnStatus = "KO"; 1.946 + if(deleteContact(rawContactId)) { 1.947 + returnStatus = "OK"; 1.948 + } 1.949 + 1.950 + sendCallbackToJavascript("Android:Contact:Remove:Return:" + returnStatus, requestID, 1.951 + new String[] {"contactID"}, new Object[] {rawContactId}); 1.952 + } 1.953 + 1.954 + private void saveContact(final JSONObject contactOptions, final String requestID) { 1.955 + try { 1.956 + String reason = contactOptions.getString("reason"); 1.957 + JSONObject contact = contactOptions.getJSONObject("contact"); 1.958 + JSONObject contactProperties = contact.getJSONObject("properties"); 1.959 + 1.960 + if ("update".equals(reason)) { 1.961 + updateContact(contactProperties, contact.getLong("id"), requestID); 1.962 + } else { 1.963 + insertContact(contactProperties, requestID); 1.964 + } 1.965 + } catch (JSONException e) { 1.966 + throw new IllegalArgumentException(e); 1.967 + } 1.968 + } 1.969 + 1.970 + private void insertContact(final JSONObject contactProperties, final String requestID) throws JSONException { 1.971 + ArrayList<ContentProviderOperation> newContactOptions = new ArrayList<ContentProviderOperation>(); 1.972 + 1.973 + // Account to save the contact under 1.974 + newContactOptions.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) 1.975 + .withValue(RawContacts.ACCOUNT_NAME, mAccountName) 1.976 + .withValue(RawContacts.ACCOUNT_TYPE, mAccountType) 1.977 + .build()); 1.978 + 1.979 + List<ContentValues> newContactValues = getContactValues(contactProperties); 1.980 + 1.981 + for (ContentValues values : newContactValues) { 1.982 + newContactOptions.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) 1.983 + .withValueBackReference(Data.RAW_CONTACT_ID, 0) 1.984 + .withValues(values) 1.985 + .build()); 1.986 + } 1.987 + 1.988 + String returnStatus = "KO"; 1.989 + Long newRawContactId = new Long(-1); 1.990 + 1.991 + // Insert the contact! 1.992 + ContentProviderResult[] insertResults = applyBatch(newContactOptions); 1.993 + 1.994 + if (insertResults != null) { 1.995 + try { 1.996 + // Get the ID of the newly created contact 1.997 + newRawContactId = getRawContactIdFromContentProviderResults(insertResults); 1.998 + 1.999 + if (newRawContactId != null) { 1.1000 + returnStatus = "OK"; 1.1001 + } 1.1002 + } catch (NumberFormatException e) { 1.1003 + Log.e(LOGTAG, "NumberFormatException", e); 1.1004 + } 1.1005 + 1.1006 + Log.i(LOGTAG, "Newly created contact ID: " + newRawContactId); 1.1007 + } 1.1008 + 1.1009 + Log.i(LOGTAG, "Sending return status: " + returnStatus); 1.1010 + 1.1011 + sendCallbackToJavascript("Android:Contact:Save:Return:" + returnStatus, requestID, 1.1012 + new String[] {"contactID", "reason"}, 1.1013 + new Object[] {newRawContactId, "create"}); 1.1014 + } 1.1015 + 1.1016 + private void updateContact(final JSONObject contactProperties, final long rawContactId, final String requestID) throws JSONException { 1.1017 + // Why is updating a contact so weird and horribly inefficient? Because Android doesn't 1.1018 + // like multiple values for contact fields, but the Mozilla contacts API calls for this. 1.1019 + // This means the Android update function is essentially completely useless. Why not just 1.1020 + // delete the contact and re-insert it? Because that would change the contact ID and the 1.1021 + // Mozilla contacts API shouldn't have this behavior. The solution is to delete each 1.1022 + // row from the contacts data table that belongs to the contact, and insert the new 1.1023 + // fields. But then why not just delete all the data from the data in one go and 1.1024 + // insert the new data in another? Because if all the data relating to a contact is 1.1025 + // deleted, Android will "conviently" remove the ID making it impossible to insert data 1.1026 + // under the old ID. To work around this, we put a Mozilla contact flag in the database 1.1027 + 1.1028 + ContentProviderOperation removeOptions = ContentProviderOperation.newDelete(Data.CONTENT_URI) 1.1029 + .withSelection(Data.RAW_CONTACT_ID + "=? AND " + 1.1030 + Data.MIMETYPE + " != '" + MIMETYPE_MOZILLA_CONTACTS_FLAG + "'", 1.1031 + new String[] {String.valueOf(rawContactId)}) 1.1032 + .build(); 1.1033 + 1.1034 + ArrayList<ContentProviderOperation> removeOptionsList = new ArrayList<ContentProviderOperation>(); 1.1035 + removeOptionsList.add(removeOptions); 1.1036 + 1.1037 + ContentProviderResult[] removeResults = applyBatch(removeOptionsList); 1.1038 + 1.1039 + // Check if the remove failed 1.1040 + if (removeResults == null || !checkForPositiveCountInResults(removeResults)) { 1.1041 + Log.w(LOGTAG, "Null or 0 remove results"); 1.1042 + 1.1043 + sendCallbackToJavascript("Android:Contact:Save:Return:KO", requestID, null, null); 1.1044 + return; 1.1045 + } 1.1046 + 1.1047 + List<ContentValues> updateContactValues = getContactValues(contactProperties); 1.1048 + ArrayList<ContentProviderOperation> updateContactOptions = new ArrayList<ContentProviderOperation>(); 1.1049 + 1.1050 + for (ContentValues values : updateContactValues) { 1.1051 + updateContactOptions.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) 1.1052 + .withValue(Data.RAW_CONTACT_ID, rawContactId) 1.1053 + .withValues(values) 1.1054 + .build()); 1.1055 + } 1.1056 + 1.1057 + String returnStatus = "KO"; 1.1058 + 1.1059 + // Update the contact! 1.1060 + applyBatch(updateContactOptions); 1.1061 + 1.1062 + sendCallbackToJavascript("Android:Contact:Save:Return:OK", requestID, 1.1063 + new String[] {"contactID", "reason"}, 1.1064 + new Object[] {rawContactId, "update"}); 1.1065 + } 1.1066 + 1.1067 + private List<ContentValues> getContactValues(final JSONObject contactProperties) throws JSONException { 1.1068 + List<ContentValues> contactValues = new ArrayList<ContentValues>(); 1.1069 + 1.1070 + // Add the contact to the default group so it is shown in other apps 1.1071 + // like the Contacts or People app 1.1072 + ContentValues defaultGroupValues = new ContentValues(); 1.1073 + defaultGroupValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 1.1074 + defaultGroupValues.put(GroupMembership.GROUP_ROW_ID, mGroupId); 1.1075 + contactValues.add(defaultGroupValues); 1.1076 + 1.1077 + // Create all the values that will be inserted into the new contact 1.1078 + getNameValues(contactProperties.optJSONArray("name"), 1.1079 + contactProperties.optJSONArray("givenName"), 1.1080 + contactProperties.optJSONArray("familyName"), 1.1081 + contactProperties.optJSONArray("honorificPrefix"), 1.1082 + contactProperties.optJSONArray("honorificSuffix"), 1.1083 + contactValues); 1.1084 + 1.1085 + getGenericValues(MIMETYPE_ADDITIONAL_NAME, CUSTOM_DATA_COLUMN, 1.1086 + contactProperties.optJSONArray("additionalName"), contactValues); 1.1087 + 1.1088 + getNicknamesValues(contactProperties.optJSONArray("nickname"), contactValues); 1.1089 + 1.1090 + getAddressesValues(contactProperties.optJSONArray("adr"), contactValues); 1.1091 + 1.1092 + getPhonesValues(contactProperties.optJSONArray("tel"), contactValues); 1.1093 + 1.1094 + getEmailsValues(contactProperties.optJSONArray("email"), contactValues); 1.1095 + 1.1096 + //getPhotosValues(contactProperties.optJSONArray("photo"), contactValues); 1.1097 + 1.1098 + getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.COMPANY, 1.1099 + contactProperties.optJSONArray("org"), contactValues); 1.1100 + 1.1101 + getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.TITLE, 1.1102 + contactProperties.optJSONArray("jobTitle"), contactValues); 1.1103 + 1.1104 + getNotesValues(contactProperties.optJSONArray("note"), contactValues); 1.1105 + 1.1106 + getWebsitesValues(contactProperties.optJSONArray("url"), contactValues); 1.1107 + 1.1108 + getImsValues(contactProperties.optJSONArray("impp"), contactValues); 1.1109 + 1.1110 + getCategoriesValues(contactProperties.optJSONArray("category"), contactValues); 1.1111 + 1.1112 + getEventValues(contactProperties.optString("bday"), Event.TYPE_BIRTHDAY, contactValues); 1.1113 + 1.1114 + getEventValues(contactProperties.optString("anniversary"), Event.TYPE_ANNIVERSARY, contactValues); 1.1115 + 1.1116 + getCustomMimetypeValues(contactProperties.optString("sex"), MIMETYPE_SEX, contactValues); 1.1117 + 1.1118 + getCustomMimetypeValues(contactProperties.optString("genderIdentity"), MIMETYPE_GENDER_IDENTITY, contactValues); 1.1119 + 1.1120 + getGenericValues(MIMETYPE_KEY, CUSTOM_DATA_COLUMN, contactProperties.optJSONArray("key"), 1.1121 + contactValues); 1.1122 + 1.1123 + return contactValues; 1.1124 + } 1.1125 + 1.1126 + private void getGenericValues(final String mimeType, final String dataType, final JSONArray fields, 1.1127 + List<ContentValues> newContactValues) throws JSONException { 1.1128 + if (fields == null) { 1.1129 + return; 1.1130 + } 1.1131 + 1.1132 + for (int i = 0; i < fields.length(); i++) { 1.1133 + ContentValues contentValues = new ContentValues(); 1.1134 + contentValues.put(Data.MIMETYPE, mimeType); 1.1135 + contentValues.put(dataType, fields.getString(i)); 1.1136 + newContactValues.add(contentValues); 1.1137 + } 1.1138 + } 1.1139 + 1.1140 + private void getNameValues(final JSONArray displayNames, final JSONArray givenNames, 1.1141 + final JSONArray familyNames, final JSONArray prefixes, 1.1142 + final JSONArray suffixes, List<ContentValues> newContactValues) throws JSONException { 1.1143 + int maxLen = max((displayNames != null ? displayNames.length() : 0), 1.1144 + (givenNames != null ? givenNames.length() : 0), 1.1145 + (familyNames != null ? familyNames.length() : 0), 1.1146 + (prefixes != null ? prefixes.length() : 0), 1.1147 + (suffixes != null ? suffixes.length() : 0)); 1.1148 + 1.1149 + for (int i = 0; i < maxLen; i++) { 1.1150 + ContentValues contentValues = new ContentValues(); 1.1151 + contentValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 1.1152 + 1.1153 + final String displayName = (displayNames != null ? displayNames.optString(i, null) : null); 1.1154 + final String givenName = (givenNames != null ? givenNames.optString(i, null) : null); 1.1155 + final String familyName = (familyNames != null ? familyNames.optString(i, null) : null); 1.1156 + final String prefix = (prefixes != null ? prefixes.optString(i, null) : null); 1.1157 + final String suffix = (suffixes != null ? suffixes.optString(i, null) : null); 1.1158 + 1.1159 + if (displayName != null) { 1.1160 + contentValues.put(StructuredName.DISPLAY_NAME, displayName); 1.1161 + } 1.1162 + if (givenName != null) { 1.1163 + contentValues.put(StructuredName.GIVEN_NAME, givenName); 1.1164 + } 1.1165 + if (familyName != null) { 1.1166 + contentValues.put(StructuredName.FAMILY_NAME, familyName); 1.1167 + } 1.1168 + if (prefix != null) { 1.1169 + contentValues.put(StructuredName.PREFIX, prefix); 1.1170 + } 1.1171 + if (suffix != null) { 1.1172 + contentValues.put(StructuredName.SUFFIX, suffix); 1.1173 + } 1.1174 + 1.1175 + newContactValues.add(contentValues); 1.1176 + } 1.1177 + } 1.1178 + 1.1179 + private void getNicknamesValues(final JSONArray nicknames, List<ContentValues> newContactValues) throws JSONException { 1.1180 + if (nicknames == null) { 1.1181 + return; 1.1182 + } 1.1183 + 1.1184 + for (int i = 0; i < nicknames.length(); i++) { 1.1185 + ContentValues contentValues = new ContentValues(); 1.1186 + contentValues.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); 1.1187 + contentValues.put(Nickname.NAME, nicknames.getString(i)); 1.1188 + contentValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT); 1.1189 + newContactValues.add(contentValues); 1.1190 + } 1.1191 + } 1.1192 + 1.1193 + private void getAddressesValues(final JSONArray addresses, List<ContentValues> newContactValues) throws JSONException { 1.1194 + if (addresses == null) { 1.1195 + return; 1.1196 + } 1.1197 + 1.1198 + for (int i = 0; i < addresses.length(); i++) { 1.1199 + JSONObject address = addresses.getJSONObject(i); 1.1200 + JSONArray addressTypes = address.optJSONArray("type"); 1.1201 + 1.1202 + if (addressTypes != null) { 1.1203 + for (int j = 0; j < addressTypes.length(); j++) { 1.1204 + // Translate the address type string to an integer constant 1.1205 + // provided by the ContactsContract API 1.1206 + final String type = addressTypes.getString(j); 1.1207 + final int typeConstant = getAddressType(type); 1.1208 + 1.1209 + newContactValues.add(createAddressContentValues(address, typeConstant, type)); 1.1210 + } 1.1211 + } else { 1.1212 + newContactValues.add(createAddressContentValues(address, -1, null)); 1.1213 + } 1.1214 + } 1.1215 + } 1.1216 + 1.1217 + private ContentValues createAddressContentValues(final JSONObject address, final int typeConstant, 1.1218 + final String type) throws JSONException { 1.1219 + ContentValues contentValues = new ContentValues(); 1.1220 + contentValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); 1.1221 + contentValues.put(StructuredPostal.STREET, address.optString("streetAddress")); 1.1222 + contentValues.put(StructuredPostal.CITY, address.optString("locality")); 1.1223 + contentValues.put(StructuredPostal.REGION, address.optString("region")); 1.1224 + contentValues.put(StructuredPostal.POSTCODE, address.optString("postalCode")); 1.1225 + contentValues.put(StructuredPostal.COUNTRY, address.optString("countryName")); 1.1226 + 1.1227 + if (type != null) { 1.1228 + contentValues.put(StructuredPostal.TYPE, typeConstant); 1.1229 + 1.1230 + // If a custom type, add a label 1.1231 + if (typeConstant == BaseTypes.TYPE_CUSTOM) { 1.1232 + contentValues.put(StructuredPostal.LABEL, type); 1.1233 + } 1.1234 + } 1.1235 + 1.1236 + if (address.has("pref")) { 1.1237 + contentValues.put(Data.IS_SUPER_PRIMARY, address.getBoolean("pref") ? 1 : 0); 1.1238 + } 1.1239 + 1.1240 + return contentValues; 1.1241 + } 1.1242 + 1.1243 + private void getPhonesValues(final JSONArray phones, List<ContentValues> newContactValues) throws JSONException { 1.1244 + if (phones == null) { 1.1245 + return; 1.1246 + } 1.1247 + 1.1248 + for (int i = 0; i < phones.length(); i++) { 1.1249 + JSONObject phone = phones.getJSONObject(i); 1.1250 + JSONArray phoneTypes = phone.optJSONArray("type"); 1.1251 + ContentValues contentValues; 1.1252 + 1.1253 + if (phoneTypes != null && phoneTypes.length() > 0) { 1.1254 + for (int j = 0; j < phoneTypes.length(); j++) { 1.1255 + // Translate the phone type string to an integer constant 1.1256 + // provided by the ContactsContract API 1.1257 + final String type = phoneTypes.getString(j); 1.1258 + final int typeConstant = getPhoneType(type); 1.1259 + 1.1260 + contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"), 1.1261 + typeConstant, type, phone.optBoolean("pref")); 1.1262 + if (phone.has("carrier")) { 1.1263 + contentValues.put(CARRIER_COLUMN, phone.optString("carrier")); 1.1264 + } 1.1265 + newContactValues.add(contentValues); 1.1266 + } 1.1267 + } else { 1.1268 + contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"), 1.1269 + -1, null, phone.optBoolean("pref")); 1.1270 + if (phone.has("carrier")) { 1.1271 + contentValues.put(CARRIER_COLUMN, phone.optString("carrier")); 1.1272 + } 1.1273 + newContactValues.add(contentValues); 1.1274 + } 1.1275 + } 1.1276 + } 1.1277 + 1.1278 + private void getEmailsValues(final JSONArray emails, List<ContentValues> newContactValues) throws JSONException { 1.1279 + if (emails == null) { 1.1280 + return; 1.1281 + } 1.1282 + 1.1283 + for (int i = 0; i < emails.length(); i++) { 1.1284 + JSONObject email = emails.getJSONObject(i); 1.1285 + JSONArray emailTypes = email.optJSONArray("type"); 1.1286 + 1.1287 + if (emailTypes != null && emailTypes.length() > 0) { 1.1288 + for (int j = 0; j < emailTypes.length(); j++) { 1.1289 + // Translate the email type string to an integer constant 1.1290 + // provided by the ContactsContract API 1.1291 + final String type = emailTypes.getString(j); 1.1292 + final int typeConstant = getEmailType(type); 1.1293 + 1.1294 + newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE, 1.1295 + email.optString("value"), 1.1296 + typeConstant, type, 1.1297 + email.optBoolean("pref"))); 1.1298 + } 1.1299 + } else { 1.1300 + newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE, 1.1301 + email.optString("value"), 1.1302 + -1, null, email.optBoolean("pref"))); 1.1303 + } 1.1304 + } 1.1305 + } 1.1306 + 1.1307 + private void getPhotosValues(final JSONArray photos, List<ContentValues> newContactValues) throws JSONException { 1.1308 + if (photos == null) { 1.1309 + return; 1.1310 + } 1.1311 + 1.1312 + // TODO: implement this 1.1313 + } 1.1314 + 1.1315 + private void getNotesValues(final JSONArray notes, List<ContentValues> newContactValues) throws JSONException { 1.1316 + if (notes == null) { 1.1317 + return; 1.1318 + } 1.1319 + 1.1320 + for (int i = 0; i < notes.length(); i++) { 1.1321 + ContentValues contentValues = new ContentValues(); 1.1322 + contentValues.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE); 1.1323 + contentValues.put(Note.NOTE, notes.getString(i)); 1.1324 + newContactValues.add(contentValues); 1.1325 + } 1.1326 + } 1.1327 + 1.1328 + private void getWebsitesValues(final JSONArray websites, List<ContentValues> newContactValues) throws JSONException { 1.1329 + if (websites == null) { 1.1330 + return; 1.1331 + } 1.1332 + 1.1333 + for (int i = 0; i < websites.length(); i++) { 1.1334 + JSONObject website = websites.getJSONObject(i); 1.1335 + JSONArray websiteTypes = website.optJSONArray("type"); 1.1336 + 1.1337 + if (websiteTypes != null && websiteTypes.length() > 0) { 1.1338 + for (int j = 0; j < websiteTypes.length(); j++) { 1.1339 + // Translate the website type string to an integer constant 1.1340 + // provided by the ContactsContract API 1.1341 + final String type = websiteTypes.getString(j); 1.1342 + final int typeConstant = getWebsiteType(type); 1.1343 + 1.1344 + newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE, 1.1345 + website.optString("value"), 1.1346 + typeConstant, type, 1.1347 + website.optBoolean("pref"))); 1.1348 + } 1.1349 + } else { 1.1350 + newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE, 1.1351 + website.optString("value"), 1.1352 + -1, null, website.optBoolean("pref"))); 1.1353 + } 1.1354 + } 1.1355 + } 1.1356 + 1.1357 + private void getImsValues(final JSONArray ims, List<ContentValues> newContactValues) throws JSONException { 1.1358 + if (ims == null) { 1.1359 + return; 1.1360 + } 1.1361 + 1.1362 + for (int i = 0; i < ims.length(); i++) { 1.1363 + JSONObject im = ims.getJSONObject(i); 1.1364 + JSONArray imTypes = im.optJSONArray("type"); 1.1365 + 1.1366 + if (imTypes != null && imTypes.length() > 0) { 1.1367 + for (int j = 0; j < imTypes.length(); j++) { 1.1368 + // Translate the IM type string to an integer constant 1.1369 + // provided by the ContactsContract API 1.1370 + final String type = imTypes.getString(j); 1.1371 + final int typeConstant = getImType(type); 1.1372 + 1.1373 + newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE, 1.1374 + im.optString("value"), 1.1375 + typeConstant, type, 1.1376 + im.optBoolean("pref"))); 1.1377 + } 1.1378 + } else { 1.1379 + newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE, 1.1380 + im.optString("value"), 1.1381 + -1, null, im.optBoolean("pref"))); 1.1382 + } 1.1383 + } 1.1384 + } 1.1385 + 1.1386 + private void getCategoriesValues(final JSONArray categories, List<ContentValues> newContactValues) throws JSONException { 1.1387 + if (categories == null) { 1.1388 + return; 1.1389 + } 1.1390 + 1.1391 + for (int i = 0; i < categories.length(); i++) { 1.1392 + String category = categories.getString(i); 1.1393 + 1.1394 + if ("my contacts".equals(category.toLowerCase()) || 1.1395 + PRE_HONEYCOMB_DEFAULT_GROUP.equalsIgnoreCase(category)) { 1.1396 + Log.w(LOGTAG, "New contacts are implicitly added to the default group."); 1.1397 + continue; 1.1398 + } 1.1399 + 1.1400 + // Find the group ID of the given category 1.1401 + long groupId = getGroupId(category); 1.1402 + 1.1403 + // Create the group if it doesn't already exist 1.1404 + if (groupId == -1) { 1.1405 + groupId = createGroup(category); 1.1406 + // If the group is still -1, we failed to create the group 1.1407 + if (groupId == -1) { 1.1408 + // Only log the category name if in debug 1.1409 + if (DEBUG) { 1.1410 + Log.d(LOGTAG, "Failed to create new group for category \"" + category + "\""); 1.1411 + } else { 1.1412 + Log.w(LOGTAG, "Failed to create new group for given category."); 1.1413 + } 1.1414 + continue; 1.1415 + } 1.1416 + } 1.1417 + 1.1418 + ContentValues contentValues = new ContentValues(); 1.1419 + contentValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 1.1420 + contentValues.put(GroupMembership.GROUP_ROW_ID, groupId); 1.1421 + newContactValues.add(contentValues); 1.1422 + 1.1423 + newContactValues.add(contentValues); 1.1424 + } 1.1425 + } 1.1426 + 1.1427 + private void getEventValues(final String event, final int type, List<ContentValues> newContactValues) { 1.1428 + if (event == null || event.length() < 11) { 1.1429 + return; 1.1430 + } 1.1431 + 1.1432 + ContentValues contentValues = new ContentValues(); 1.1433 + contentValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); 1.1434 + contentValues.put(Event.START_DATE, event.substring(0, 10)); 1.1435 + contentValues.put(Event.TYPE, type); 1.1436 + newContactValues.add(contentValues); 1.1437 + } 1.1438 + 1.1439 + private void getCustomMimetypeValues(final String value, final String mimeType, List<ContentValues> newContactValues) { 1.1440 + if (value == null || "null".equals(value)) { 1.1441 + return; 1.1442 + } 1.1443 + 1.1444 + ContentValues contentValues = new ContentValues(); 1.1445 + contentValues.put(Data.MIMETYPE, mimeType); 1.1446 + contentValues.put(CUSTOM_DATA_COLUMN, value); 1.1447 + newContactValues.add(contentValues); 1.1448 + } 1.1449 + 1.1450 + private void getMozillaContactFlagValues(List<ContentValues> newContactValues) { 1.1451 + try { 1.1452 + JSONArray mozillaContactsFlag = new JSONArray(); 1.1453 + mozillaContactsFlag.put("1"); 1.1454 + getGenericValues(MIMETYPE_MOZILLA_CONTACTS_FLAG, CUSTOM_DATA_COLUMN, mozillaContactsFlag, newContactValues); 1.1455 + } catch (JSONException e) { 1.1456 + throw new IllegalArgumentException(e); 1.1457 + } 1.1458 + } 1.1459 + 1.1460 + private ContentValues createContentValues(final String mimeType, final String value, final int typeConstant, 1.1461 + final String type, final boolean preferredValue) { 1.1462 + ContentValues contentValues = new ContentValues(); 1.1463 + contentValues.put(Data.MIMETYPE, mimeType); 1.1464 + contentValues.put(Data.DATA1, value); 1.1465 + contentValues.put(Data.IS_SUPER_PRIMARY, preferredValue ? 1 : 0); 1.1466 + 1.1467 + if (type != null) { 1.1468 + contentValues.put(Data.DATA2, typeConstant); 1.1469 + 1.1470 + // If a custom type, add a label 1.1471 + if (typeConstant == BaseTypes.TYPE_CUSTOM) { 1.1472 + contentValues.put(Data.DATA3, type); 1.1473 + } 1.1474 + } 1.1475 + 1.1476 + return contentValues; 1.1477 + } 1.1478 + 1.1479 + private void getContactsCount(final String requestID) { 1.1480 + Cursor cursor = getAllRawContactIdsCursor(); 1.1481 + Integer numContacts = Integer.valueOf(cursor.getCount()); 1.1482 + cursor.close(); 1.1483 + 1.1484 + sendCallbackToJavascript("Android:Contacts:Count", requestID, new String[] {"count"}, 1.1485 + new Object[] {numContacts}); 1.1486 + } 1.1487 + 1.1488 + private void sendCallbackToJavascript(final String subject, final String requestID, 1.1489 + final String[] argNames, final Object[] argValues) { 1.1490 + // Check the same number of argument names and arguments were given 1.1491 + if (argNames != null && argNames.length != argValues.length) { 1.1492 + throw new IllegalArgumentException("Argument names and argument values lengths do not match. " + 1.1493 + "Names length = " + argNames.length + ", Values length = " + 1.1494 + argValues.length); 1.1495 + } 1.1496 + 1.1497 + try { 1.1498 + JSONObject callbackMessage = new JSONObject(); 1.1499 + callbackMessage.put("requestID", requestID); 1.1500 + 1.1501 + if (argNames != null) { 1.1502 + for (int i = 0; i < argNames.length; i++) { 1.1503 + callbackMessage.put(argNames[i], argValues[i]); 1.1504 + } 1.1505 + } 1.1506 + 1.1507 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(subject, callbackMessage.toString())); 1.1508 + } catch (JSONException e) { 1.1509 + throw new IllegalArgumentException(e); 1.1510 + } 1.1511 + } 1.1512 + 1.1513 + private void registerEventListener(final String event) { 1.1514 + mEventDispatcher.registerEventListener(event, this); 1.1515 + } 1.1516 + 1.1517 + private void unregisterEventListener(final String event) { 1.1518 + mEventDispatcher.unregisterEventListener(event, this); 1.1519 + } 1.1520 + 1.1521 + private ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) { 1.1522 + try { 1.1523 + return mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations); 1.1524 + } catch (RemoteException e) { 1.1525 + Log.e(LOGTAG, "RemoteException", e); 1.1526 + } catch (OperationApplicationException e) { 1.1527 + Log.e(LOGTAG, "OperationApplicationException", e); 1.1528 + } 1.1529 + return null; 1.1530 + } 1.1531 + 1.1532 + private void getDeviceAccount(final Runnable handleMessage) { 1.1533 + Account[] accounts = AccountManager.get(mActivity).getAccounts(); 1.1534 + 1.1535 + if (accounts.length == 0) { 1.1536 + Log.w(LOGTAG, "No accounts available"); 1.1537 + gotDeviceAccount(handleMessage); 1.1538 + } else if (accounts.length > 1) { 1.1539 + // Show the accounts chooser dialog if more than one dialog exists 1.1540 + showAccountsDialog(accounts, handleMessage); 1.1541 + } else { 1.1542 + // If only one account exists, use it 1.1543 + mAccountName = accounts[0].name; 1.1544 + mAccountType = accounts[0].type; 1.1545 + gotDeviceAccount(handleMessage); 1.1546 + } 1.1547 + 1.1548 + mGotDeviceAccount = true; 1.1549 + } 1.1550 + 1.1551 + private void showAccountsDialog(final Account[] accounts, final Runnable handleMessage) { 1.1552 + String[] accountNames = new String[accounts.length]; 1.1553 + for (int i = 0; i < accounts.length; i++) { 1.1554 + accountNames[i] = accounts[i].name; 1.1555 + } 1.1556 + 1.1557 + final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); 1.1558 + builder.setTitle(mActivity.getResources().getString(R.string.contacts_account_chooser_dialog_title)) 1.1559 + .setSingleChoiceItems(accountNames, 0, new DialogInterface.OnClickListener() { 1.1560 + @Override 1.1561 + public void onClick(DialogInterface dialog, int position) { 1.1562 + // Set the account name and type when an item is selected and dismiss the dialog 1.1563 + mAccountName = accounts[position].name; 1.1564 + mAccountType = accounts[position].type; 1.1565 + dialog.dismiss(); 1.1566 + gotDeviceAccount(handleMessage); 1.1567 + } 1.1568 + }); 1.1569 + 1.1570 + mActivity.runOnUiThread(new Runnable() { 1.1571 + public void run() { 1.1572 + builder.show(); 1.1573 + } 1.1574 + }); 1.1575 + } 1.1576 + 1.1577 + private void gotDeviceAccount(final Runnable handleMessage) { 1.1578 + // Force the handleMessage runnable and getDefaultGroupId to run on the background thread 1.1579 + Runnable runnable = new Runnable() { 1.1580 + @Override 1.1581 + public void run() { 1.1582 + getDefaultGroupId(); 1.1583 + 1.1584 + // Don't log a user's account if not debug mode. Otherwise, just log a message 1.1585 + // saying that we got an account to use 1.1586 + if (mAccountName == null) { 1.1587 + Log.i(LOGTAG, "No device account selected. Leaving account as null."); 1.1588 + } else if (DEBUG) { 1.1589 + Log.d(LOGTAG, "Using account: " + mAccountName + " (type: " + mAccountType + ")"); 1.1590 + } else { 1.1591 + Log.i(LOGTAG, "Got device account to use for contact operations."); 1.1592 + } 1.1593 + handleMessage.run(); 1.1594 + } 1.1595 + }; 1.1596 + 1.1597 + ThreadUtils.postToBackgroundThread(runnable); 1.1598 + } 1.1599 + 1.1600 + private void getDefaultGroupId() { 1.1601 + Cursor cursor = getAllGroups(); 1.1602 + 1.1603 + cursor.moveToPosition(-1); 1.1604 + while (cursor.moveToNext()) { 1.1605 + // Check if the account name and type for the group match the account name and type of 1.1606 + // the account we're working with 1.1607 + final String groupAccountName = cursor.getString(GROUP_ACCOUNT_NAME); 1.1608 + if (!groupAccountName.equals(mAccountName)) { 1.1609 + continue; 1.1610 + } 1.1611 + 1.1612 + final String groupAccountType = cursor.getString(GROUP_ACCOUNT_TYPE); 1.1613 + if (!groupAccountType.equals(mAccountType)) { 1.1614 + continue; 1.1615 + } 1.1616 + 1.1617 + // For all honeycomb and up, the default group is the first one which has the AUTO_ADD flag set 1.1618 + if (isAutoAddGroup(cursor)) { 1.1619 + mGroupTitle = cursor.getString(GROUP_TITLE); 1.1620 + mGroupId = cursor.getLong(GROUP_ID); 1.1621 + break; 1.1622 + } else if (PRE_HONEYCOMB_DEFAULT_GROUP.equals(cursor.getString(GROUP_TITLE))) { 1.1623 + mGroupId = cursor.getLong(GROUP_ID); 1.1624 + mGroupTitle = PRE_HONEYCOMB_DEFAULT_GROUP; 1.1625 + break; 1.1626 + } 1.1627 + } 1.1628 + cursor.close(); 1.1629 + 1.1630 + if (mGroupId == 0) { 1.1631 + Log.w(LOGTAG, "Default group ID not found. Newly created contacts will not belong to any groups."); 1.1632 + } else if (DEBUG) { 1.1633 + Log.i(LOGTAG, "Using group ID: " + mGroupId + " (" + mGroupTitle + ")"); 1.1634 + } 1.1635 + } 1.1636 + 1.1637 + private static boolean isAutoAddGroup(Cursor cursor) { 1.1638 + // For Honeycomb and up, the default group is the first one which has the AUTO_ADD flag set. 1.1639 + // For everything below Honeycomb, use the default "System Group: My Contacts" group 1.1640 + return (Build.VERSION.SDK_INT >= 11 && !cursor.isNull(GROUP_AUTO_ADD) && 1.1641 + cursor.getInt(GROUP_AUTO_ADD) != 0); 1.1642 + } 1.1643 + 1.1644 + private long getGroupId(String groupName) { 1.1645 + long groupId = -1; 1.1646 + Cursor cursor = getGroups(Groups.TITLE + " = '" + groupName + "'"); 1.1647 + 1.1648 + cursor.moveToPosition(-1); 1.1649 + while (cursor.moveToNext()) { 1.1650 + String groupAccountName = cursor.getString(GROUP_ACCOUNT_NAME); 1.1651 + String groupAccountType = cursor.getString(GROUP_ACCOUNT_TYPE); 1.1652 + 1.1653 + // Check if the account name and type for the group match the account name and type of 1.1654 + // the account we're working with or the default "Phone" account if no account was found 1.1655 + if (groupAccountName.equals(mAccountName) && groupAccountType.equals(mAccountType) || 1.1656 + (mAccountName == null && "Phone".equals(groupAccountType))) { 1.1657 + if (groupName.equals(cursor.getString(GROUP_TITLE))) { 1.1658 + groupId = cursor.getLong(GROUP_ID); 1.1659 + break; 1.1660 + } 1.1661 + } 1.1662 + } 1.1663 + cursor.close(); 1.1664 + 1.1665 + return groupId; 1.1666 + } 1.1667 + 1.1668 + private String getGroupName(long groupId) { 1.1669 + Cursor cursor = getGroups(Groups._ID + " = " + groupId); 1.1670 + 1.1671 + if (cursor.getCount() == 0) { 1.1672 + cursor.close(); 1.1673 + return null; 1.1674 + } 1.1675 + 1.1676 + cursor.moveToPosition(0); 1.1677 + String groupName = cursor.getString(cursor.getColumnIndex(Groups.TITLE)); 1.1678 + cursor.close(); 1.1679 + 1.1680 + return groupName; 1.1681 + } 1.1682 + 1.1683 + private Cursor getAllGroups() { 1.1684 + return getGroups(null); 1.1685 + } 1.1686 + 1.1687 + private Cursor getGroups(String selectArg) { 1.1688 + String[] columns = new String[] { 1.1689 + Groups.ACCOUNT_NAME, 1.1690 + Groups.ACCOUNT_TYPE, 1.1691 + Groups._ID, 1.1692 + Groups.TITLE, 1.1693 + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Groups.AUTO_ADD : Groups._ID) 1.1694 + }; 1.1695 + 1.1696 + if (selectArg != null) { 1.1697 + selectArg = "AND " + selectArg; 1.1698 + } else { 1.1699 + selectArg = ""; 1.1700 + } 1.1701 + 1.1702 + return mContentResolver.query(Groups.CONTENT_URI, columns, 1.1703 + Groups.ACCOUNT_TYPE + " NOT NULL AND " + 1.1704 + Groups.ACCOUNT_NAME + " NOT NULL " + selectArg, null, null); 1.1705 + } 1.1706 + 1.1707 + private long createGroup(String groupName) { 1.1708 + if (DEBUG) { 1.1709 + Log.d(LOGTAG, "Creating group: " + groupName); 1.1710 + } 1.1711 + 1.1712 + ArrayList<ContentProviderOperation> newGroupOptions = new ArrayList<ContentProviderOperation>(); 1.1713 + 1.1714 + // Create the group under the account we're using 1.1715 + // If no account is selected, use a default account name/type for the group 1.1716 + newGroupOptions.add(ContentProviderOperation.newInsert(Groups.CONTENT_URI) 1.1717 + .withValue(Groups.ACCOUNT_NAME, (mAccountName == null ? "Phone" : mAccountName)) 1.1718 + .withValue(Groups.ACCOUNT_TYPE, (mAccountType == null ? "Phone" : mAccountType)) 1.1719 + .withValue(Groups.TITLE, groupName) 1.1720 + .withValue(Groups.GROUP_VISIBLE, true) 1.1721 + .build()); 1.1722 + 1.1723 + applyBatch(newGroupOptions); 1.1724 + 1.1725 + // Return the ID of the newly created group 1.1726 + return getGroupId(groupName); 1.1727 + } 1.1728 + 1.1729 + private long[] getAllRawContactIds() { 1.1730 + Cursor cursor = getAllRawContactIdsCursor(); 1.1731 + 1.1732 + // Put the ids into an array 1.1733 + long[] ids = new long[cursor.getCount()]; 1.1734 + int index = 0; 1.1735 + cursor.moveToPosition(-1); 1.1736 + while(cursor.moveToNext()) { 1.1737 + ids[index] = cursor.getLong(cursor.getColumnIndex(RawContacts._ID)); 1.1738 + index++; 1.1739 + } 1.1740 + cursor.close(); 1.1741 + 1.1742 + return ids; 1.1743 + } 1.1744 + 1.1745 + private Cursor getAllRawContactIdsCursor() { 1.1746 + // When a contact is deleted, it actually just sets the deleted field to 1 until the 1.1747 + // sync adapter actually deletes the contact later so ignore any contacts with the deleted 1.1748 + // flag set 1.1749 + String selection = RawContacts.DELETED + "=0"; 1.1750 + String[] selectionArgs = null; 1.1751 + 1.1752 + // Only get contacts from the selected account 1.1753 + if (mAccountName != null) { 1.1754 + selection += " AND " + RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?"; 1.1755 + selectionArgs = new String[] {mAccountName, mAccountType}; 1.1756 + } 1.1757 + 1.1758 + // Get the ID's of all contacts and use the number of contact ID's as 1.1759 + // the total number of contacts 1.1760 + return mContentResolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID}, 1.1761 + selection, selectionArgs, null); 1.1762 + } 1.1763 + 1.1764 + private static Long getRawContactIdFromContentProviderResults(ContentProviderResult[] results) throws NumberFormatException { 1.1765 + for (int i = 0; i < results.length; i++) { 1.1766 + if (results[i].uri == null) { 1.1767 + continue; 1.1768 + } 1.1769 + 1.1770 + String uri = results[i].uri.toString(); 1.1771 + // Check if the uri is from the raw contacts table 1.1772 + if (uri.contains("raw_contacts")) { 1.1773 + // The ID is the after the final forward slash in the URI 1.1774 + return Long.parseLong(uri.substring(uri.lastIndexOf("/") + 1)); 1.1775 + } 1.1776 + } 1.1777 + 1.1778 + return null; 1.1779 + } 1.1780 + 1.1781 + private static boolean checkForPositiveCountInResults(ContentProviderResult[] results) { 1.1782 + for (int i = 0; i < results.length; i++) { 1.1783 + Integer count = results[i].count; 1.1784 + 1.1785 + if (DEBUG) { 1.1786 + Log.d(LOGTAG, "Results count: " + count); 1.1787 + } 1.1788 + 1.1789 + if (count != null && count > 0) { 1.1790 + return true; 1.1791 + } 1.1792 + } 1.1793 + 1.1794 + return false; 1.1795 + } 1.1796 + 1.1797 + private static long[] convertLongListToArray(List<Long> list) { 1.1798 + long[] array = new long[list.size()]; 1.1799 + 1.1800 + for (int i = 0; i < list.size(); i++) { 1.1801 + array[i] = list.get(i); 1.1802 + } 1.1803 + 1.1804 + return array; 1.1805 + } 1.1806 + 1.1807 + private static boolean doesJSONArrayContainString(final JSONArray array, final String value) { 1.1808 + for (int i = 0; i < array.length(); i++) { 1.1809 + if (value.equals(array.optString(i))) { 1.1810 + return true; 1.1811 + } 1.1812 + } 1.1813 + 1.1814 + return false; 1.1815 + } 1.1816 + 1.1817 + private static int max(int... values) { 1.1818 + int max = values[0]; 1.1819 + for (int value : values) { 1.1820 + if (value > max) { 1.1821 + max = value; 1.1822 + } 1.1823 + } 1.1824 + return max; 1.1825 + } 1.1826 + 1.1827 + private static void putPossibleNullValueInJSONObject(final String key, final Object value, JSONObject jsonObject) throws JSONException{ 1.1828 + if (value != null) { 1.1829 + jsonObject.put(key, value); 1.1830 + } else { 1.1831 + jsonObject.put(key, JSONObject.NULL); 1.1832 + } 1.1833 + } 1.1834 + 1.1835 + private static String getKeyFromMapValue(final HashMap<String, Integer> map, Integer value) { 1.1836 + for (Entry<String, Integer> entry : map.entrySet()) { 1.1837 + if (value == entry.getValue()) { 1.1838 + return entry.getKey(); 1.1839 + } 1.1840 + } 1.1841 + return null; 1.1842 + } 1.1843 + 1.1844 + private String getColumnNameConstant(String field) { 1.1845 + initColumnNameConstantsMap(); 1.1846 + return mColumnNameConstantsMap.get(field.toLowerCase()); 1.1847 + } 1.1848 + 1.1849 + private void initColumnNameConstantsMap() { 1.1850 + if (mColumnNameConstantsMap != null) { 1.1851 + return; 1.1852 + } 1.1853 + mColumnNameConstantsMap = new HashMap<String, String>(); 1.1854 + 1.1855 + mColumnNameConstantsMap.put("name", StructuredName.DISPLAY_NAME); 1.1856 + mColumnNameConstantsMap.put("givenname", StructuredName.GIVEN_NAME); 1.1857 + mColumnNameConstantsMap.put("familyname", StructuredName.FAMILY_NAME); 1.1858 + mColumnNameConstantsMap.put("honorificprefix", StructuredName.PREFIX); 1.1859 + mColumnNameConstantsMap.put("honorificsuffix", StructuredName.SUFFIX); 1.1860 + mColumnNameConstantsMap.put("additionalname", CUSTOM_DATA_COLUMN); 1.1861 + mColumnNameConstantsMap.put("nickname", Nickname.NAME); 1.1862 + mColumnNameConstantsMap.put("adr", StructuredPostal.STREET); 1.1863 + mColumnNameConstantsMap.put("email", Email.ADDRESS); 1.1864 + mColumnNameConstantsMap.put("url", Website.URL); 1.1865 + mColumnNameConstantsMap.put("category", GroupMembership.GROUP_ROW_ID); 1.1866 + mColumnNameConstantsMap.put("tel", Phone.NUMBER); 1.1867 + mColumnNameConstantsMap.put("org", Organization.COMPANY); 1.1868 + mColumnNameConstantsMap.put("jobTitle", Organization.TITLE); 1.1869 + mColumnNameConstantsMap.put("note", Note.NOTE); 1.1870 + mColumnNameConstantsMap.put("impp", Im.DATA); 1.1871 + mColumnNameConstantsMap.put("sex", CUSTOM_DATA_COLUMN); 1.1872 + mColumnNameConstantsMap.put("genderidentity", CUSTOM_DATA_COLUMN); 1.1873 + mColumnNameConstantsMap.put("key", CUSTOM_DATA_COLUMN); 1.1874 + } 1.1875 + 1.1876 + private String getMimeTypeOfField(String field) { 1.1877 + initMimeTypeConstantsMap(); 1.1878 + return mMimeTypeConstantsMap.get(field.toLowerCase()); 1.1879 + } 1.1880 + 1.1881 + private void initMimeTypeConstantsMap() { 1.1882 + if (mMimeTypeConstantsMap != null) { 1.1883 + return; 1.1884 + } 1.1885 + mMimeTypeConstantsMap = new HashMap<String, String>(); 1.1886 + 1.1887 + mMimeTypeConstantsMap.put("name", StructuredName.CONTENT_ITEM_TYPE); 1.1888 + mMimeTypeConstantsMap.put("givenname", StructuredName.CONTENT_ITEM_TYPE); 1.1889 + mMimeTypeConstantsMap.put("familyname", StructuredName.CONTENT_ITEM_TYPE); 1.1890 + mMimeTypeConstantsMap.put("honorificprefix", StructuredName.CONTENT_ITEM_TYPE); 1.1891 + mMimeTypeConstantsMap.put("honorificsuffix", StructuredName.CONTENT_ITEM_TYPE); 1.1892 + mMimeTypeConstantsMap.put("additionalname", MIMETYPE_ADDITIONAL_NAME); 1.1893 + mMimeTypeConstantsMap.put("nickname", Nickname.CONTENT_ITEM_TYPE); 1.1894 + mMimeTypeConstantsMap.put("email", Email.CONTENT_ITEM_TYPE); 1.1895 + mMimeTypeConstantsMap.put("url", Website.CONTENT_ITEM_TYPE); 1.1896 + mMimeTypeConstantsMap.put("category", GroupMembership.CONTENT_ITEM_TYPE); 1.1897 + mMimeTypeConstantsMap.put("tel", Phone.CONTENT_ITEM_TYPE); 1.1898 + mMimeTypeConstantsMap.put("org", Organization.CONTENT_ITEM_TYPE); 1.1899 + mMimeTypeConstantsMap.put("jobTitle", Organization.CONTENT_ITEM_TYPE); 1.1900 + mMimeTypeConstantsMap.put("note", Note.CONTENT_ITEM_TYPE); 1.1901 + mMimeTypeConstantsMap.put("impp", Im.CONTENT_ITEM_TYPE); 1.1902 + mMimeTypeConstantsMap.put("sex", MIMETYPE_SEX); 1.1903 + mMimeTypeConstantsMap.put("genderidentity", MIMETYPE_GENDER_IDENTITY); 1.1904 + mMimeTypeConstantsMap.put("key", MIMETYPE_KEY); 1.1905 + } 1.1906 + 1.1907 + private int getAddressType(String addressType) { 1.1908 + initAddressTypesMap(); 1.1909 + Integer type = mAddressTypesMap.get(addressType.toLowerCase()); 1.1910 + return (type != null ? Integer.valueOf(type) : StructuredPostal.TYPE_CUSTOM); 1.1911 + } 1.1912 + 1.1913 + private void initAddressTypesMap() { 1.1914 + if (mAddressTypesMap != null) { 1.1915 + return; 1.1916 + } 1.1917 + mAddressTypesMap = new HashMap<String, Integer>(); 1.1918 + 1.1919 + mAddressTypesMap.put("home", StructuredPostal.TYPE_HOME); 1.1920 + mAddressTypesMap.put("work", StructuredPostal.TYPE_WORK); 1.1921 + } 1.1922 + 1.1923 + private int getPhoneType(String phoneType) { 1.1924 + initPhoneTypesMap(); 1.1925 + Integer type = mPhoneTypesMap.get(phoneType.toLowerCase()); 1.1926 + return (type != null ? Integer.valueOf(type) : Phone.TYPE_CUSTOM); 1.1927 + } 1.1928 + 1.1929 + private void initPhoneTypesMap() { 1.1930 + if (mPhoneTypesMap != null) { 1.1931 + return; 1.1932 + } 1.1933 + mPhoneTypesMap = new HashMap<String, Integer>(); 1.1934 + 1.1935 + mPhoneTypesMap.put("home", Phone.TYPE_HOME); 1.1936 + mPhoneTypesMap.put("mobile", Phone.TYPE_MOBILE); 1.1937 + mPhoneTypesMap.put("work", Phone.TYPE_WORK); 1.1938 + mPhoneTypesMap.put("fax home", Phone.TYPE_FAX_HOME); 1.1939 + mPhoneTypesMap.put("fax work", Phone.TYPE_FAX_WORK); 1.1940 + mPhoneTypesMap.put("pager", Phone.TYPE_PAGER); 1.1941 + mPhoneTypesMap.put("callback", Phone.TYPE_CALLBACK); 1.1942 + mPhoneTypesMap.put("car", Phone.TYPE_CAR); 1.1943 + mPhoneTypesMap.put("company main", Phone.TYPE_COMPANY_MAIN); 1.1944 + mPhoneTypesMap.put("isdn", Phone.TYPE_ISDN); 1.1945 + mPhoneTypesMap.put("main", Phone.TYPE_MAIN); 1.1946 + mPhoneTypesMap.put("fax other", Phone.TYPE_OTHER_FAX); 1.1947 + mPhoneTypesMap.put("other fax", Phone.TYPE_OTHER_FAX); 1.1948 + mPhoneTypesMap.put("radio", Phone.TYPE_RADIO); 1.1949 + mPhoneTypesMap.put("telex", Phone.TYPE_TELEX); 1.1950 + mPhoneTypesMap.put("tty", Phone.TYPE_TTY_TDD); 1.1951 + mPhoneTypesMap.put("ttd", Phone.TYPE_TTY_TDD); 1.1952 + mPhoneTypesMap.put("work mobile", Phone.TYPE_WORK_MOBILE); 1.1953 + mPhoneTypesMap.put("work pager", Phone.TYPE_WORK_PAGER); 1.1954 + mPhoneTypesMap.put("assistant", Phone.TYPE_ASSISTANT); 1.1955 + mPhoneTypesMap.put("mms", Phone.TYPE_MMS); 1.1956 + } 1.1957 + 1.1958 + private int getEmailType(String emailType) { 1.1959 + initEmailTypesMap(); 1.1960 + Integer type = mEmailTypesMap.get(emailType.toLowerCase()); 1.1961 + return (type != null ? Integer.valueOf(type) : Email.TYPE_CUSTOM); 1.1962 + } 1.1963 + 1.1964 + private void initEmailTypesMap() { 1.1965 + if (mEmailTypesMap != null) { 1.1966 + return; 1.1967 + } 1.1968 + mEmailTypesMap = new HashMap<String, Integer>(); 1.1969 + 1.1970 + mEmailTypesMap.put("home", Email.TYPE_HOME); 1.1971 + mEmailTypesMap.put("mobile", Email.TYPE_MOBILE); 1.1972 + mEmailTypesMap.put("work", Email.TYPE_WORK); 1.1973 + } 1.1974 + 1.1975 + private int getWebsiteType(String webisteType) { 1.1976 + initWebsiteTypesMap(); 1.1977 + Integer type = mWebsiteTypesMap.get(webisteType.toLowerCase()); 1.1978 + return (type != null ? Integer.valueOf(type) : Website.TYPE_CUSTOM); 1.1979 + } 1.1980 + 1.1981 + private void initWebsiteTypesMap() { 1.1982 + if (mWebsiteTypesMap != null) { 1.1983 + return; 1.1984 + } 1.1985 + mWebsiteTypesMap = new HashMap<String, Integer>(); 1.1986 + 1.1987 + mWebsiteTypesMap.put("homepage", Website.TYPE_HOMEPAGE); 1.1988 + mWebsiteTypesMap.put("blog", Website.TYPE_BLOG); 1.1989 + mWebsiteTypesMap.put("profile", Website.TYPE_PROFILE); 1.1990 + mWebsiteTypesMap.put("home", Website.TYPE_HOME); 1.1991 + mWebsiteTypesMap.put("work", Website.TYPE_WORK); 1.1992 + mWebsiteTypesMap.put("ftp", Website.TYPE_FTP); 1.1993 + } 1.1994 + 1.1995 + private int getImType(String imType) { 1.1996 + initImTypesMap(); 1.1997 + Integer type = mImTypesMap.get(imType.toLowerCase()); 1.1998 + return (type != null ? Integer.valueOf(type) : Im.TYPE_CUSTOM); 1.1999 + } 1.2000 + 1.2001 + private void initImTypesMap() { 1.2002 + if (mImTypesMap != null) { 1.2003 + return; 1.2004 + } 1.2005 + mImTypesMap = new HashMap<String, Integer>(); 1.2006 + 1.2007 + mImTypesMap.put("home", Im.TYPE_HOME); 1.2008 + mImTypesMap.put("work", Im.TYPE_WORK); 1.2009 + } 1.2010 + 1.2011 + private String[] getAllColumns() { 1.2012 + return new String[] {Entity.DATA_ID, Data.MIMETYPE, Data.IS_SUPER_PRIMARY, 1.2013 + Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, 1.2014 + Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8, 1.2015 + Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12, 1.2016 + Data.DATA13, Data.DATA14, Data.DATA15}; 1.2017 + } 1.2018 +}