mobile/android/base/ContactService.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko;
michael@0 6
michael@0 7 import java.util.ArrayList;
michael@0 8 import java.util.Collections;
michael@0 9 import java.util.Comparator;
michael@0 10 import java.util.HashMap;
michael@0 11 import java.util.List;
michael@0 12 import java.util.Map.Entry;
michael@0 13
michael@0 14 import org.json.JSONArray;
michael@0 15 import org.json.JSONException;
michael@0 16 import org.json.JSONObject;
michael@0 17 import org.mozilla.gecko.util.GeckoEventListener;
michael@0 18 import org.mozilla.gecko.util.ThreadUtils;
michael@0 19
michael@0 20 import android.accounts.Account;
michael@0 21 import android.accounts.AccountManager;
michael@0 22 import android.app.AlertDialog;
michael@0 23 import android.content.ContentProviderOperation;
michael@0 24 import android.content.ContentProviderResult;
michael@0 25 import android.content.ContentResolver;
michael@0 26 import android.content.ContentUris;
michael@0 27 import android.content.ContentValues;
michael@0 28 import android.content.DialogInterface;
michael@0 29 import android.content.OperationApplicationException;
michael@0 30 import android.database.Cursor;
michael@0 31 import android.net.Uri;
michael@0 32 import android.os.Build;
michael@0 33 import android.os.RemoteException;
michael@0 34 import android.provider.ContactsContract;
michael@0 35 import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
michael@0 36 import android.provider.ContactsContract.CommonDataKinds.Email;
michael@0 37 import android.provider.ContactsContract.CommonDataKinds.Event;
michael@0 38 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
michael@0 39 import android.provider.ContactsContract.CommonDataKinds.Im;
michael@0 40 import android.provider.ContactsContract.CommonDataKinds.Nickname;
michael@0 41 import android.provider.ContactsContract.CommonDataKinds.Note;
michael@0 42 import android.provider.ContactsContract.CommonDataKinds.Organization;
michael@0 43 import android.provider.ContactsContract.CommonDataKinds.Phone;
michael@0 44 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
michael@0 45 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
michael@0 46 import android.provider.ContactsContract.CommonDataKinds.Website;
michael@0 47 import android.provider.ContactsContract.Data;
michael@0 48 import android.provider.ContactsContract.Groups;
michael@0 49 import android.provider.ContactsContract.RawContacts;
michael@0 50 import android.provider.ContactsContract.RawContacts.Entity;
michael@0 51 import android.telephony.PhoneNumberUtils;
michael@0 52 import android.util.Log;
michael@0 53
michael@0 54 public class ContactService implements GeckoEventListener {
michael@0 55 private static final String LOGTAG = "GeckoContactService";
michael@0 56 private static final boolean DEBUG = false;
michael@0 57
michael@0 58 private final static int GROUP_ACCOUNT_NAME = 0;
michael@0 59 private final static int GROUP_ACCOUNT_TYPE = 1;
michael@0 60 private final static int GROUP_ID = 2;
michael@0 61 private final static int GROUP_TITLE = 3;
michael@0 62 private final static int GROUP_AUTO_ADD = 4;
michael@0 63
michael@0 64 private final static String CARRIER_COLUMN = Data.DATA5;
michael@0 65 private final static String CUSTOM_DATA_COLUMN = Data.DATA1;
michael@0 66
michael@0 67 // Pre-Honeycomb versions of Android have a "My Contacts" system group that all contacts are
michael@0 68 // assigned to by default for a given account. After Honeycomb, an AUTO_ADD database column
michael@0 69 // was added to denote groups that contacts are automatically added to
michael@0 70 private final static String PRE_HONEYCOMB_DEFAULT_GROUP = "System Group: My Contacts";
michael@0 71 private final static String MIMETYPE_ADDITIONAL_NAME = "org.mozilla.gecko/additional_name";
michael@0 72 private final static String MIMETYPE_SEX = "org.mozilla.gecko/sex";
michael@0 73 private final static String MIMETYPE_GENDER_IDENTITY = "org.mozilla.gecko/gender_identity";
michael@0 74 private final static String MIMETYPE_KEY = "org.mozilla.gecko/key";
michael@0 75 private final static String MIMETYPE_MOZILLA_CONTACTS_FLAG = "org.mozilla.gecko/contact_flag";
michael@0 76
michael@0 77 private final EventDispatcher mEventDispatcher;
michael@0 78
michael@0 79 private String mAccountName;
michael@0 80 private String mAccountType;
michael@0 81 private String mGroupTitle;
michael@0 82 private long mGroupId;
michael@0 83 private boolean mGotDeviceAccount;
michael@0 84
michael@0 85 private HashMap<String, String> mColumnNameConstantsMap;
michael@0 86 private HashMap<String, String> mMimeTypeConstantsMap;
michael@0 87 private HashMap<String, Integer> mAddressTypesMap;
michael@0 88 private HashMap<String, Integer> mPhoneTypesMap;
michael@0 89 private HashMap<String, Integer> mEmailTypesMap;
michael@0 90 private HashMap<String, Integer> mWebsiteTypesMap;
michael@0 91 private HashMap<String, Integer> mImTypesMap;
michael@0 92
michael@0 93 private ContentResolver mContentResolver;
michael@0 94 private GeckoApp mActivity;
michael@0 95
michael@0 96 ContactService(EventDispatcher eventDispatcher, GeckoApp activity) {
michael@0 97 mEventDispatcher = eventDispatcher;
michael@0 98 mActivity = activity;
michael@0 99 mContentResolver = mActivity.getContentResolver();
michael@0 100 mGotDeviceAccount = false;
michael@0 101
michael@0 102 registerEventListener("Android:Contacts:Clear");
michael@0 103 registerEventListener("Android:Contacts:Find");
michael@0 104 registerEventListener("Android:Contacts:GetAll");
michael@0 105 registerEventListener("Android:Contacts:GetCount");
michael@0 106 registerEventListener("Android:Contact:Remove");
michael@0 107 registerEventListener("Android:Contact:Save");
michael@0 108 }
michael@0 109
michael@0 110 public void destroy() {
michael@0 111 unregisterEventListener("Android:Contacts:Clear");
michael@0 112 unregisterEventListener("Android:Contacts:Find");
michael@0 113 unregisterEventListener("Android:Contacts:GetAll");
michael@0 114 unregisterEventListener("Android:Contacts:GetCount");
michael@0 115 unregisterEventListener("Android:Contact:Remove");
michael@0 116 unregisterEventListener("Android:Contact:Save");
michael@0 117 }
michael@0 118
michael@0 119 @Override
michael@0 120 public void handleMessage(final String event, final JSONObject message) {
michael@0 121 // If the account chooser dialog needs shown to the user, the message handling becomes
michael@0 122 // asychronous so it needs posted to a background thread from the UI thread when the
michael@0 123 // account chooser dialog is dismissed by the user.
michael@0 124 Runnable handleMessage = new Runnable() {
michael@0 125 @Override
michael@0 126 public void run() {
michael@0 127 try {
michael@0 128 if (DEBUG) {
michael@0 129 Log.d(LOGTAG, "Event: " + event + "\nMessage: " + message.toString(3));
michael@0 130 }
michael@0 131
michael@0 132 final JSONObject messageData = message.getJSONObject("data");
michael@0 133 final String requestID = messageData.getString("requestID");
michael@0 134
michael@0 135 // Options may not exist for all operations
michael@0 136 JSONObject contactOptions = messageData.optJSONObject("options");
michael@0 137
michael@0 138 if ("Android:Contacts:Find".equals(event)) {
michael@0 139 findContacts(contactOptions, requestID);
michael@0 140 } else if ("Android:Contacts:GetAll".equals(event)) {
michael@0 141 getAllContacts(messageData, requestID);
michael@0 142 } else if ("Android:Contacts:Clear".equals(event)) {
michael@0 143 clearAllContacts(contactOptions, requestID);
michael@0 144 } else if ("Android:Contact:Save".equals(event)) {
michael@0 145 saveContact(contactOptions, requestID);
michael@0 146 } else if ("Android:Contact:Remove".equals(event)) {
michael@0 147 removeContact(contactOptions, requestID);
michael@0 148 } else if ("Android:Contacts:GetCount".equals(event)) {
michael@0 149 getContactsCount(requestID);
michael@0 150 } else {
michael@0 151 throw new IllegalArgumentException("Unexpected event: " + event);
michael@0 152 }
michael@0 153 } catch (JSONException e) {
michael@0 154 throw new IllegalArgumentException("Message: " + e);
michael@0 155 }
michael@0 156 }
michael@0 157 };
michael@0 158
michael@0 159 // Get the account name/type if they haven't been set yet
michael@0 160 if (!mGotDeviceAccount) {
michael@0 161 getDeviceAccount(handleMessage);
michael@0 162 } else {
michael@0 163 handleMessage.run();
michael@0 164 }
michael@0 165 }
michael@0 166
michael@0 167 private void findContacts(final JSONObject contactOptions, final String requestID) {
michael@0 168 long[] rawContactIds = findContactsRawIds(contactOptions);
michael@0 169 Log.i(LOGTAG, "Got " + (rawContactIds != null ? rawContactIds.length : "null") + " raw contact IDs");
michael@0 170
michael@0 171 final String[] sortOptions = getSortOptionsFromJSON(contactOptions);
michael@0 172
michael@0 173 if (rawContactIds == null || sortOptions == null) {
michael@0 174 sendCallbackToJavascript("Android:Contacts:Find:Return:KO", requestID, null, null);
michael@0 175 } else {
michael@0 176 sendCallbackToJavascript("Android:Contacts:Find:Return:OK", requestID,
michael@0 177 new String[] {"contacts"},
michael@0 178 new Object[] {getContactsAsJSONArray(rawContactIds, sortOptions[0],
michael@0 179 sortOptions[1])});
michael@0 180 }
michael@0 181 }
michael@0 182
michael@0 183 private void getAllContacts(final JSONObject contactOptions, final String requestID) {
michael@0 184 long[] rawContactIds = getAllRawContactIds();
michael@0 185 Log.i(LOGTAG, "Got " + rawContactIds.length + " raw contact IDs");
michael@0 186
michael@0 187 final String[] sortOptions = getSortOptionsFromJSON(contactOptions);
michael@0 188
michael@0 189 if (rawContactIds == null || sortOptions == null) {
michael@0 190 // There's no failure message for getAll
michael@0 191 return;
michael@0 192 } else {
michael@0 193 sendCallbackToJavascript("Android:Contacts:GetAll:Next", requestID,
michael@0 194 new String[] {"contacts"},
michael@0 195 new Object[] {getContactsAsJSONArray(rawContactIds, sortOptions[0],
michael@0 196 sortOptions[1])});
michael@0 197 }
michael@0 198 }
michael@0 199
michael@0 200 private static String[] getSortOptionsFromJSON(final JSONObject contactOptions) {
michael@0 201 String sortBy = null;
michael@0 202 String sortOrder = null;
michael@0 203
michael@0 204 try {
michael@0 205 final JSONObject findOptions = contactOptions.getJSONObject("findOptions");
michael@0 206 sortBy = findOptions.optString("sortBy").toLowerCase();
michael@0 207 sortOrder = findOptions.optString("sortOrder").toLowerCase();
michael@0 208
michael@0 209 if ("".equals(sortBy)) {
michael@0 210 sortBy = null;
michael@0 211 }
michael@0 212 if ("".equals(sortOrder)) {
michael@0 213 sortOrder = "ascending";
michael@0 214 }
michael@0 215
michael@0 216 // Only "familyname" and "givenname" are valid sortBy values and only "ascending"
michael@0 217 // and "descending" are valid sortOrder values
michael@0 218 if ((sortBy != null && !"familyname".equals(sortBy) && !"givenname".equals(sortBy)) ||
michael@0 219 (!"ascending".equals(sortOrder) && !"descending".equals(sortOrder))) {
michael@0 220 return null;
michael@0 221 }
michael@0 222 } catch (JSONException e) {
michael@0 223 throw new IllegalArgumentException(e);
michael@0 224 }
michael@0 225
michael@0 226 return new String[] {sortBy, sortOrder};
michael@0 227 }
michael@0 228
michael@0 229 private long[] findContactsRawIds(final JSONObject contactOptions) {
michael@0 230 List<Long> rawContactIds = new ArrayList<Long>();
michael@0 231 Cursor cursor = null;
michael@0 232
michael@0 233 try {
michael@0 234 final JSONObject findOptions = contactOptions.getJSONObject("findOptions");
michael@0 235 String filterValue = findOptions.optString("filterValue");
michael@0 236 JSONArray filterBy = findOptions.optJSONArray("filterBy");
michael@0 237 final String filterOp = findOptions.optString("filterOp");
michael@0 238 final int filterLimit = findOptions.getInt("filterLimit");
michael@0 239 final int substringMatching = findOptions.getInt("substringMatching");
michael@0 240
michael@0 241 // If filter value is undefined, avoid all the logic below and just return
michael@0 242 // all available raw contact IDs
michael@0 243 if ("".equals(filterValue) || "".equals(filterOp)) {
michael@0 244 long[] allRawContactIds = getAllRawContactIds();
michael@0 245
michael@0 246 // Truncate the raw contacts IDs array if necessary
michael@0 247 if (filterLimit > 0 && allRawContactIds.length > filterLimit) {
michael@0 248 long[] truncatedRawContactIds = new long[filterLimit];
michael@0 249 for (int i = 0; i < filterLimit; i++) {
michael@0 250 truncatedRawContactIds[i] = allRawContactIds[i];
michael@0 251 }
michael@0 252 return truncatedRawContactIds;
michael@0 253 }
michael@0 254 return allRawContactIds;
michael@0 255 }
michael@0 256
michael@0 257 // "match" can only be used with the "tel" field
michael@0 258 if ("match".equals(filterOp)) {
michael@0 259 for (int i = 0; i < filterBy.length(); i++) {
michael@0 260 if (!"tel".equals(filterBy.getString(i))) {
michael@0 261 Log.w(LOGTAG, "\"match\" filterBy option is only valid for the \"tel\" field");
michael@0 262 return null;
michael@0 263 }
michael@0 264 }
michael@0 265 }
michael@0 266
michael@0 267 // Only select contacts from the selected account
michael@0 268 String selection = null;
michael@0 269 String[] selectionArgs = null;
michael@0 270
michael@0 271 if (mAccountName != null) {
michael@0 272 selection = RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?";
michael@0 273 selectionArgs = new String[] {mAccountName, mAccountType};
michael@0 274 }
michael@0 275
michael@0 276
michael@0 277 final String[] columnsToGet;
michael@0 278
michael@0 279 // If a filterBy value was not specified, search all columns
michael@0 280 if (filterBy == null || filterBy.length() == 0) {
michael@0 281 columnsToGet = null;
michael@0 282 } else {
michael@0 283 // Only get the columns given in the filterBy array
michael@0 284 List<String> columnsToGetList = new ArrayList<String>();
michael@0 285
michael@0 286 columnsToGetList.add(Data.RAW_CONTACT_ID);
michael@0 287 columnsToGetList.add(Data.MIMETYPE);
michael@0 288 for (int i = 0; i < filterBy.length(); i++) {
michael@0 289 final String field = filterBy.getString(i);
michael@0 290
michael@0 291 // If one of the filterBy fields is the ID, just return the filter value
michael@0 292 // which should be the ID
michael@0 293 if ("id".equals(field)) {
michael@0 294 try {
michael@0 295 return new long[] {Long.valueOf(filterValue)};
michael@0 296 } catch (NumberFormatException e) {
michael@0 297 // If the ID couldn't be converted to a long, it's invalid data
michael@0 298 // so return null for failure
michael@0 299 return null;
michael@0 300 }
michael@0 301 }
michael@0 302
michael@0 303 final String columnName = getColumnNameConstant(field);
michael@0 304
michael@0 305 if (columnName != null) {
michael@0 306 columnsToGetList.add(columnName);
michael@0 307 } else {
michael@0 308 Log.w(LOGTAG, "Unknown filter option: " + field);
michael@0 309 }
michael@0 310 }
michael@0 311
michael@0 312 columnsToGet = columnsToGetList.toArray(new String[columnsToGetList.size()]);
michael@0 313 }
michael@0 314
michael@0 315 // Execute the query
michael@0 316 cursor = mContentResolver.query(Data.CONTENT_URI, columnsToGet, selection,
michael@0 317 selectionArgs, null);
michael@0 318
michael@0 319 if (cursor.getCount() > 0) {
michael@0 320 cursor.moveToPosition(-1);
michael@0 321 while (cursor.moveToNext()) {
michael@0 322 String mimeType = cursor.getString(cursor.getColumnIndex(Data.MIMETYPE));
michael@0 323
michael@0 324 // Check if the current mimetype is one of the types to filter by
michael@0 325 if (filterBy != null && filterBy.length() > 0) {
michael@0 326 for (int i = 0; i < filterBy.length(); i++) {
michael@0 327 String currentFilterBy = filterBy.getString(i);
michael@0 328
michael@0 329 if (mimeType.equals(getMimeTypeOfField(currentFilterBy))) {
michael@0 330 String columnName = getColumnNameConstant(currentFilterBy);
michael@0 331 int columnIndex = cursor.getColumnIndex(columnName);
michael@0 332 String databaseValue = cursor.getString(columnIndex);
michael@0 333
michael@0 334 boolean isPhone = false;
michael@0 335 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 336 isPhone = true;
michael@0 337 } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 338 // Translate the group ID to the group name for matching
michael@0 339 try {
michael@0 340 databaseValue = getGroupName(Long.valueOf(databaseValue));
michael@0 341 } catch (NumberFormatException e) {
michael@0 342 Log.e(LOGTAG, "Number Format Exception", e);
michael@0 343 continue;
michael@0 344 }
michael@0 345 } else if (databaseValue == null) {
michael@0 346 continue;
michael@0 347 }
michael@0 348
michael@0 349 // Check if the value matches the filter value
michael@0 350 if (isFindMatch(filterOp, filterValue, databaseValue, isPhone, substringMatching)) {
michael@0 351 addMatchToList(cursor, rawContactIds);
michael@0 352 break;
michael@0 353 }
michael@0 354 }
michael@0 355 }
michael@0 356 } else {
michael@0 357 // If no filterBy options were given, check each column for a match
michael@0 358 int numColumns = cursor.getColumnCount();
michael@0 359 for (int i = 0; i < numColumns; i++) {
michael@0 360 String databaseValue = cursor.getString(i);
michael@0 361 if (databaseValue != null && isFindMatch(filterOp, filterValue, databaseValue, false, substringMatching)) {
michael@0 362 addMatchToList(cursor, rawContactIds);
michael@0 363 break;
michael@0 364 }
michael@0 365 }
michael@0 366 }
michael@0 367
michael@0 368 // If the max found contacts size has been hit, stop looking for contacts
michael@0 369 // A filter limit of 0 denotes there is no limit
michael@0 370 if (filterLimit > 0 && filterLimit <= rawContactIds.size()) {
michael@0 371 break;
michael@0 372 }
michael@0 373 }
michael@0 374 }
michael@0 375 } catch (JSONException e) {
michael@0 376 throw new IllegalArgumentException(e);
michael@0 377 } finally {
michael@0 378 if (cursor != null) {
michael@0 379 cursor.close();
michael@0 380 }
michael@0 381 }
michael@0 382
michael@0 383 // Return the contact IDs list converted to an array
michael@0 384 return convertLongListToArray(rawContactIds);
michael@0 385 }
michael@0 386
michael@0 387 private boolean isFindMatch(final String filterOp, String filterValue, String databaseValue,
michael@0 388 final boolean isPhone, final int substringMatching) {
michael@0 389 Log.i(LOGTAG, "matching: filterOp: " + filterOp);
michael@0 390 if (DEBUG) {
michael@0 391 Log.d(LOGTAG, "matching: filterValue: " + filterValue);
michael@0 392 Log.d(LOGTAG, "matching: databaseValue: " + databaseValue);
michael@0 393 }
michael@0 394 Log.i(LOGTAG, "matching: isPhone: " + isPhone);
michael@0 395 Log.i(LOGTAG, "matching: substringMatching: " + substringMatching);
michael@0 396
michael@0 397 if (databaseValue == null) {
michael@0 398 return false;
michael@0 399 }
michael@0 400
michael@0 401 filterValue = filterValue.toLowerCase();
michael@0 402 databaseValue = databaseValue.toLowerCase();
michael@0 403
michael@0 404 if ("match".equals(filterOp)) {
michael@0 405 // If substring matching is a positive number, only pay attention to the last X characters
michael@0 406 // of both the filter and database values
michael@0 407 if (substringMatching > 0) {
michael@0 408 databaseValue = substringStartFromEnd(cleanPhoneNumber(databaseValue), substringMatching);
michael@0 409 filterValue = substringStartFromEnd(cleanPhoneNumber(filterValue), substringMatching);
michael@0 410 return databaseValue.startsWith(filterValue);
michael@0 411 }
michael@0 412 return databaseValue.equals(filterValue);
michael@0 413 } else if ("equals".equals(filterOp)) {
michael@0 414 if (isPhone) {
michael@0 415 return PhoneNumberUtils.compare(filterValue, databaseValue);
michael@0 416 }
michael@0 417 return databaseValue.equals(filterValue);
michael@0 418 } else if ("contains".equals(filterOp)) {
michael@0 419 if (isPhone) {
michael@0 420 filterValue = cleanPhoneNumber(filterValue);
michael@0 421 databaseValue = cleanPhoneNumber(databaseValue);
michael@0 422 }
michael@0 423 return databaseValue.contains(filterValue);
michael@0 424 } else if ("startsWith".equals(filterOp)) {
michael@0 425 // If a phone number, remove non-dialable characters and then only pay attention to
michael@0 426 // the last X digits given by the substring matching values (see bug 877302)
michael@0 427 if (isPhone) {
michael@0 428 String cleanedDatabasePhone = cleanPhoneNumber(databaseValue);
michael@0 429 if (substringMatching > 0) {
michael@0 430 cleanedDatabasePhone = substringStartFromEnd(cleanedDatabasePhone, substringMatching);
michael@0 431 }
michael@0 432
michael@0 433 if (cleanedDatabasePhone.startsWith(filterValue)) {
michael@0 434 return true;
michael@0 435 }
michael@0 436 }
michael@0 437 return databaseValue.startsWith(filterValue);
michael@0 438 }
michael@0 439 return false;
michael@0 440 }
michael@0 441
michael@0 442 private static String cleanPhoneNumber(String phone) {
michael@0 443 return phone.replace(" ", "").replace("(", "").replace(")", "").replace("-", "");
michael@0 444 }
michael@0 445
michael@0 446 private static String substringStartFromEnd(final String string, final int distanceFromEnd) {
michael@0 447 int stringLen = string.length();
michael@0 448 if (stringLen < distanceFromEnd) {
michael@0 449 return string;
michael@0 450 }
michael@0 451 return string.substring(stringLen - distanceFromEnd);
michael@0 452 }
michael@0 453
michael@0 454 private static void addMatchToList(final Cursor cursor, List<Long> rawContactIds) {
michael@0 455 long rawContactId = cursor.getLong(cursor.getColumnIndex(Data.RAW_CONTACT_ID));
michael@0 456 if (!rawContactIds.contains(rawContactId)) {
michael@0 457 rawContactIds.add(rawContactId);
michael@0 458 }
michael@0 459 }
michael@0 460
michael@0 461 private JSONArray getContactsAsJSONArray(final long[] rawContactIds, final String sortBy, final String sortOrder) {
michael@0 462 List<JSONObject> contactsList = new ArrayList<JSONObject>();
michael@0 463 JSONArray contactsArray = new JSONArray();
michael@0 464
michael@0 465 // Get each contact as a JSON object
michael@0 466 for (int i = 0; i < rawContactIds.length; i++) {
michael@0 467 contactsList.add(getContactAsJSONObject(rawContactIds[i]));
michael@0 468 }
michael@0 469
michael@0 470 // Sort the contacts
michael@0 471 if (sortBy != null) {
michael@0 472 Collections.sort(contactsList, new ContactsComparator(sortBy, sortOrder));
michael@0 473 }
michael@0 474
michael@0 475 // Convert the contacts list to a JSON array
michael@0 476 for (int i = 0; i < contactsList.size(); i++) {
michael@0 477 contactsArray.put(contactsList.get(i));
michael@0 478 }
michael@0 479
michael@0 480 return contactsArray;
michael@0 481 }
michael@0 482
michael@0 483 private JSONObject getContactAsJSONObject(long rawContactId) {
michael@0 484 // ContactManager wants a contact object with it's properties wrapped in an array of objects
michael@0 485 JSONObject contact = new JSONObject();
michael@0 486 JSONObject contactProperties = new JSONObject();
michael@0 487
michael@0 488 JSONArray names = new JSONArray();
michael@0 489 JSONArray givenNames = new JSONArray();
michael@0 490 JSONArray familyNames = new JSONArray();
michael@0 491 JSONArray honorificPrefixes = new JSONArray();
michael@0 492 JSONArray honorificSuffixes = new JSONArray();
michael@0 493 JSONArray additionalNames = new JSONArray();
michael@0 494 JSONArray nicknames = new JSONArray();
michael@0 495 JSONArray addresses = new JSONArray();
michael@0 496 JSONArray phones = new JSONArray();
michael@0 497 JSONArray emails = new JSONArray();
michael@0 498 JSONArray organizations = new JSONArray();
michael@0 499 JSONArray jobTitles = new JSONArray();
michael@0 500 JSONArray notes = new JSONArray();
michael@0 501 JSONArray urls = new JSONArray();
michael@0 502 JSONArray impps = new JSONArray();
michael@0 503 JSONArray categories = new JSONArray();
michael@0 504 String bday = null;
michael@0 505 String anniversary = null;
michael@0 506 String sex = null;
michael@0 507 String genderIdentity = null;
michael@0 508 JSONArray key = new JSONArray();
michael@0 509
michael@0 510 // Get all the data columns
michael@0 511 final String[] columnsToGet = getAllColumns();
michael@0 512
michael@0 513 Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
michael@0 514 Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY);
michael@0 515
michael@0 516 Cursor cursor = mContentResolver.query(entityUri, columnsToGet, null, null, null);
michael@0 517 cursor.moveToPosition(-1);
michael@0 518 while (cursor.moveToNext()) {
michael@0 519 String mimeType = cursor.getString(cursor.getColumnIndex(Data.MIMETYPE));
michael@0 520
michael@0 521 // Put the proper fields for each mimetype into the JSON arrays
michael@0 522 try {
michael@0 523 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 524 final String displayName = cursor.getString(cursor.getColumnIndex(StructuredName.DISPLAY_NAME));
michael@0 525 final String givenName = cursor.getString(cursor.getColumnIndex(StructuredName.GIVEN_NAME));
michael@0 526 final String familyName = cursor.getString(cursor.getColumnIndex(StructuredName.FAMILY_NAME));
michael@0 527 final String prefix = cursor.getString(cursor.getColumnIndex(StructuredName.PREFIX));
michael@0 528 final String suffix = cursor.getString(cursor.getColumnIndex(StructuredName.SUFFIX));
michael@0 529
michael@0 530 if (displayName != null) {
michael@0 531 names.put(displayName);
michael@0 532 }
michael@0 533 if (givenName != null) {
michael@0 534 givenNames.put(givenName);
michael@0 535 }
michael@0 536 if (familyName != null) {
michael@0 537 familyNames.put(familyName);
michael@0 538 }
michael@0 539 if (prefix != null) {
michael@0 540 honorificPrefixes.put(prefix);
michael@0 541 }
michael@0 542 if (suffix != null) {
michael@0 543 honorificSuffixes.put(suffix);
michael@0 544 }
michael@0 545
michael@0 546 } else if (MIMETYPE_ADDITIONAL_NAME.equals(mimeType)) {
michael@0 547 additionalNames.put(cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)));
michael@0 548
michael@0 549 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 550 nicknames.put(cursor.getString(cursor.getColumnIndex(Nickname.NAME)));
michael@0 551
michael@0 552 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 553 initAddressTypesMap();
michael@0 554 getAddressDataAsJSONObject(cursor, addresses);
michael@0 555
michael@0 556 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 557 initPhoneTypesMap();
michael@0 558 getPhoneDataAsJSONObject(cursor, phones);
michael@0 559
michael@0 560 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 561 initEmailTypesMap();
michael@0 562 getGenericDataAsJSONObject(cursor, emails, Email.ADDRESS, Email.TYPE, Email.LABEL, mEmailTypesMap);
michael@0 563
michael@0 564 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 565 getOrganizationDataAsJSONObject(cursor, organizations, jobTitles);
michael@0 566
michael@0 567 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 568 notes.put(cursor.getString(cursor.getColumnIndex(Note.NOTE)));
michael@0 569
michael@0 570 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 571 initWebsiteTypesMap();
michael@0 572 getGenericDataAsJSONObject(cursor, urls, Website.URL, Website.TYPE, Website.LABEL, mWebsiteTypesMap);
michael@0 573
michael@0 574 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 575 initImTypesMap();
michael@0 576 getGenericDataAsJSONObject(cursor, impps, Im.DATA, Im.TYPE, Im.LABEL, mImTypesMap);
michael@0 577
michael@0 578 } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 579 long groupId = cursor.getLong(cursor.getColumnIndex(GroupMembership.GROUP_ROW_ID));
michael@0 580 String groupName = getGroupName(groupId);
michael@0 581 if (!doesJSONArrayContainString(categories, groupName)) {
michael@0 582 categories.put(groupName);
michael@0 583 }
michael@0 584
michael@0 585 } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) {
michael@0 586 int type = cursor.getInt(cursor.getColumnIndex(Event.TYPE));
michael@0 587 String date = cursor.getString(cursor.getColumnIndex(Event.START_DATE));
michael@0 588
michael@0 589 // Add the time info onto the date so it correctly parses into a JS date object
michael@0 590 date += "T00:00:00";
michael@0 591
michael@0 592 switch (type) {
michael@0 593 case Event.TYPE_BIRTHDAY:
michael@0 594 bday = date;
michael@0 595 break;
michael@0 596
michael@0 597 case Event.TYPE_ANNIVERSARY:
michael@0 598 anniversary = date;
michael@0 599 break;
michael@0 600 }
michael@0 601
michael@0 602 } else if (MIMETYPE_SEX.equals(mimeType)) {
michael@0 603 sex = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN));
michael@0 604
michael@0 605 } else if (MIMETYPE_GENDER_IDENTITY.equals(mimeType)) {
michael@0 606 genderIdentity = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN));
michael@0 607
michael@0 608 } else if (MIMETYPE_KEY.equals(mimeType)) {
michael@0 609 key.put(cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)));
michael@0 610 }
michael@0 611 } catch (JSONException e) {
michael@0 612 throw new IllegalArgumentException(e);
michael@0 613 }
michael@0 614 }
michael@0 615 cursor.close();
michael@0 616
michael@0 617 try {
michael@0 618 // Add the fields to the contact properties object
michael@0 619 contactProperties.put("name", names);
michael@0 620 contactProperties.put("givenName", givenNames);
michael@0 621 contactProperties.put("familyName", familyNames);
michael@0 622 contactProperties.put("honorificPrefix", honorificPrefixes);
michael@0 623 contactProperties.put("honorificSuffix", honorificSuffixes);
michael@0 624 contactProperties.put("additionalName", additionalNames);
michael@0 625 contactProperties.put("nickname", nicknames);
michael@0 626 contactProperties.put("adr", addresses);
michael@0 627 contactProperties.put("tel", phones);
michael@0 628 contactProperties.put("email", emails);
michael@0 629 contactProperties.put("org", organizations);
michael@0 630 contactProperties.put("jobTitle", jobTitles);
michael@0 631 contactProperties.put("note", notes);
michael@0 632 contactProperties.put("url", urls);
michael@0 633 contactProperties.put("impp", impps);
michael@0 634 contactProperties.put("category", categories);
michael@0 635 contactProperties.put("key", key);
michael@0 636
michael@0 637 putPossibleNullValueInJSONObject("bday", bday, contactProperties);
michael@0 638 putPossibleNullValueInJSONObject("anniversary", anniversary, contactProperties);
michael@0 639 putPossibleNullValueInJSONObject("sex", sex, contactProperties);
michael@0 640 putPossibleNullValueInJSONObject("genderIdentity", genderIdentity, contactProperties);
michael@0 641
michael@0 642 // Add the raw contact ID and the properties to the contact
michael@0 643 contact.put("id", String.valueOf(rawContactId));
michael@0 644 contact.put("updated", null);
michael@0 645 contact.put("published", null);
michael@0 646 contact.put("properties", contactProperties);
michael@0 647 } catch (JSONException e) {
michael@0 648 throw new IllegalArgumentException(e);
michael@0 649 }
michael@0 650
michael@0 651 if (DEBUG) {
michael@0 652 try {
michael@0 653 Log.d(LOGTAG, "Got contact: " + contact.toString(3));
michael@0 654 } catch (JSONException e) {}
michael@0 655 }
michael@0 656
michael@0 657 return contact;
michael@0 658 }
michael@0 659
michael@0 660 private boolean bool(int integer) {
michael@0 661 return integer != 0 ? true : false;
michael@0 662 }
michael@0 663
michael@0 664 private void getGenericDataAsJSONObject(Cursor cursor, JSONArray array, final String dataColumn,
michael@0 665 final String typeColumn, final String typeLabelColumn,
michael@0 666 final HashMap<String, Integer> typeMap) throws JSONException {
michael@0 667 String value = cursor.getString(cursor.getColumnIndex(dataColumn));
michael@0 668 int typeConstant = cursor.getInt(cursor.getColumnIndex(typeColumn));
michael@0 669 String type;
michael@0 670 if (typeConstant == BaseTypes.TYPE_CUSTOM) {
michael@0 671 type = cursor.getString(cursor.getColumnIndex(typeLabelColumn));
michael@0 672 } else {
michael@0 673 type = getKeyFromMapValue(typeMap, Integer.valueOf(typeConstant));
michael@0 674 }
michael@0 675
michael@0 676 // Since an object may have multiple types, it may have already been added,
michael@0 677 // but still needs the new type added
michael@0 678 boolean found = false;
michael@0 679 if (type != null) {
michael@0 680 for (int i = 0; i < array.length(); i++) {
michael@0 681 JSONObject object = array.getJSONObject(i);
michael@0 682 if (value.equals(object.getString("value"))) {
michael@0 683 found = true;
michael@0 684
michael@0 685 JSONArray types = object.getJSONArray("type");
michael@0 686 if (!doesJSONArrayContainString(types, type)) {
michael@0 687 types.put(type);
michael@0 688 break;
michael@0 689 }
michael@0 690 }
michael@0 691 }
michael@0 692 }
michael@0 693
michael@0 694 // If an existing object wasn't found, make a new one
michael@0 695 if (!found) {
michael@0 696 JSONObject object = new JSONObject();
michael@0 697 JSONArray types = new JSONArray();
michael@0 698 object.put("value", value);
michael@0 699 types.put(type);
michael@0 700 object.put("type", types);
michael@0 701 object.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Data.IS_SUPER_PRIMARY))));
michael@0 702
michael@0 703 array.put(object);
michael@0 704 }
michael@0 705 }
michael@0 706
michael@0 707 private void getPhoneDataAsJSONObject(Cursor cursor, JSONArray phones) throws JSONException {
michael@0 708 String value = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
michael@0 709 int typeConstant = cursor.getInt(cursor.getColumnIndex(Phone.TYPE));
michael@0 710 String type;
michael@0 711 if (typeConstant == Phone.TYPE_CUSTOM) {
michael@0 712 type = cursor.getString(cursor.getColumnIndex(Phone.LABEL));
michael@0 713 } else {
michael@0 714 type = getKeyFromMapValue(mPhoneTypesMap, Integer.valueOf(typeConstant));
michael@0 715 }
michael@0 716
michael@0 717 // Since a phone may have multiple types, it may have already been added,
michael@0 718 // but still needs the new type added
michael@0 719 boolean found = false;
michael@0 720 if (type != null) {
michael@0 721 for (int i = 0; i < phones.length(); i++) {
michael@0 722 JSONObject phone = phones.getJSONObject(i);
michael@0 723 if (value.equals(phone.getString("value"))) {
michael@0 724 found = true;
michael@0 725
michael@0 726 JSONArray types = phone.getJSONArray("type");
michael@0 727 if (!doesJSONArrayContainString(types, type)) {
michael@0 728 types.put(type);
michael@0 729 break;
michael@0 730 }
michael@0 731 }
michael@0 732 }
michael@0 733 }
michael@0 734
michael@0 735 // If an existing phone wasn't found, make a new one
michael@0 736 if (!found) {
michael@0 737 JSONObject phone = new JSONObject();
michael@0 738 JSONArray types = new JSONArray();
michael@0 739 phone.put("value", value);
michael@0 740 phone.put("type", type);
michael@0 741 types.put(type);
michael@0 742 phone.put("type", types);
michael@0 743 phone.put("carrier", cursor.getString(cursor.getColumnIndex(CARRIER_COLUMN)));
michael@0 744 phone.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Phone.IS_SUPER_PRIMARY))));
michael@0 745
michael@0 746 phones.put(phone);
michael@0 747 }
michael@0 748 }
michael@0 749
michael@0 750 private void getAddressDataAsJSONObject(Cursor cursor, JSONArray addresses) throws JSONException {
michael@0 751 String streetAddress = cursor.getString(cursor.getColumnIndex(StructuredPostal.STREET));
michael@0 752 String locality = cursor.getString(cursor.getColumnIndex(StructuredPostal.CITY));
michael@0 753 String region = cursor.getString(cursor.getColumnIndex(StructuredPostal.REGION));
michael@0 754 String postalCode = cursor.getString(cursor.getColumnIndex(StructuredPostal.POSTCODE));
michael@0 755 String countryName = cursor.getString(cursor.getColumnIndex(StructuredPostal.COUNTRY));
michael@0 756 int typeConstant = cursor.getInt(cursor.getColumnIndex(StructuredPostal.TYPE));
michael@0 757 String type;
michael@0 758 if (typeConstant == StructuredPostal.TYPE_CUSTOM) {
michael@0 759 type = cursor.getString(cursor.getColumnIndex(StructuredPostal.LABEL));
michael@0 760 } else {
michael@0 761 type = getKeyFromMapValue(mAddressTypesMap, Integer.valueOf(typeConstant));
michael@0 762 }
michael@0 763
michael@0 764 // Since an email may have multiple types, it may have already been added,
michael@0 765 // but still needs the new type added
michael@0 766 boolean found = false;
michael@0 767 if (type != null) {
michael@0 768 for (int i = 0; i < addresses.length(); i++) {
michael@0 769 JSONObject address = addresses.getJSONObject(i);
michael@0 770 if (streetAddress.equals(address.getString("streetAddress")) &&
michael@0 771 locality.equals(address.getString("locality")) &&
michael@0 772 region.equals(address.getString("region")) &&
michael@0 773 countryName.equals(address.getString("countryName")) &&
michael@0 774 postalCode.equals(address.getString("postalCode"))) {
michael@0 775 found = true;
michael@0 776
michael@0 777 JSONArray types = address.getJSONArray("type");
michael@0 778 if (!doesJSONArrayContainString(types, type)) {
michael@0 779 types.put(type);
michael@0 780 break;
michael@0 781 }
michael@0 782 }
michael@0 783 }
michael@0 784 }
michael@0 785
michael@0 786 // If an existing email wasn't found, make a new one
michael@0 787 if (!found) {
michael@0 788 JSONObject address = new JSONObject();
michael@0 789 JSONArray types = new JSONArray();
michael@0 790 address.put("streetAddress", streetAddress);
michael@0 791 address.put("locality", locality);
michael@0 792 address.put("region", region);
michael@0 793 address.put("countryName", countryName);
michael@0 794 address.put("postalCode", postalCode);
michael@0 795 types.put(type);
michael@0 796 address.put("type", types);
michael@0 797 address.put("pref", bool(cursor.getInt(cursor.getColumnIndex(StructuredPostal.IS_SUPER_PRIMARY))));
michael@0 798
michael@0 799 addresses.put(address);
michael@0 800 }
michael@0 801 }
michael@0 802
michael@0 803 private void getOrganizationDataAsJSONObject(Cursor cursor, JSONArray organizations,
michael@0 804 JSONArray jobTitles) throws JSONException {
michael@0 805 int organizationColumnIndex = cursor.getColumnIndex(Organization.COMPANY);
michael@0 806 int titleColumnIndex = cursor.getColumnIndex(Organization.TITLE);
michael@0 807
michael@0 808 if (!cursor.isNull(organizationColumnIndex)) {
michael@0 809 organizations.put(cursor.getString(organizationColumnIndex));
michael@0 810 }
michael@0 811 if (!cursor.isNull(titleColumnIndex)) {
michael@0 812 jobTitles.put(cursor.getString(titleColumnIndex));
michael@0 813 }
michael@0 814 }
michael@0 815
michael@0 816 private class ContactsComparator implements Comparator<JSONObject> {
michael@0 817 final String mSortBy;
michael@0 818 final String mSortOrder;
michael@0 819
michael@0 820 public ContactsComparator(final String sortBy, final String sortOrder) {
michael@0 821 mSortBy = sortBy.toLowerCase();
michael@0 822 mSortOrder = sortOrder.toLowerCase();
michael@0 823 }
michael@0 824
michael@0 825 @Override
michael@0 826 public int compare(JSONObject left, JSONObject right) {
michael@0 827 // Determine if sorting by "family name, given name" or "given name, family name"
michael@0 828 boolean familyFirst = false;
michael@0 829 if ("familyname".equals(mSortBy)) {
michael@0 830 familyFirst = true;
michael@0 831 }
michael@0 832
michael@0 833 JSONObject leftProperties;
michael@0 834 JSONObject rightProperties;
michael@0 835 try {
michael@0 836 leftProperties = left.getJSONObject("properties");
michael@0 837 rightProperties = right.getJSONObject("properties");
michael@0 838 } catch (JSONException e) {
michael@0 839 throw new IllegalArgumentException(e);
michael@0 840 }
michael@0 841
michael@0 842 JSONArray leftFamilyNames = leftProperties.optJSONArray("familyName");
michael@0 843 JSONArray leftGivenNames = leftProperties.optJSONArray("givenName");
michael@0 844 JSONArray rightFamilyNames = rightProperties.optJSONArray("familyName");
michael@0 845 JSONArray rightGivenNames = rightProperties.optJSONArray("givenName");
michael@0 846
michael@0 847 // If any of the name arrays didn't exist (are null), create empty arrays
michael@0 848 // to avoid doing a bunch of null checking below
michael@0 849 if (leftFamilyNames == null) {
michael@0 850 leftFamilyNames = new JSONArray();
michael@0 851 }
michael@0 852 if (leftGivenNames == null) {
michael@0 853 leftGivenNames = new JSONArray();
michael@0 854 }
michael@0 855 if (rightFamilyNames == null) {
michael@0 856 rightFamilyNames = new JSONArray();
michael@0 857 }
michael@0 858 if (rightGivenNames == null) {
michael@0 859 rightGivenNames = new JSONArray();
michael@0 860 }
michael@0 861
michael@0 862 int maxArrayLength = max(leftFamilyNames.length(), leftGivenNames.length(),
michael@0 863 rightFamilyNames.length(), rightGivenNames.length());
michael@0 864
michael@0 865 int index = 0;
michael@0 866 int compareResult;
michael@0 867 do {
michael@0 868 // Join together the given name and family name per the pattern above
michael@0 869 String leftName = "";
michael@0 870 String rightName = "";
michael@0 871
michael@0 872 if (familyFirst) {
michael@0 873 leftName = leftFamilyNames.optString(index, "") + leftGivenNames.optString(index, "");
michael@0 874 rightName = rightFamilyNames.optString(index, "") + rightGivenNames.optString(index, "");
michael@0 875 } else {
michael@0 876 leftName = leftGivenNames.optString(index, "") + leftFamilyNames.optString(index, "");
michael@0 877 rightName = rightGivenNames.optString(index, "") + rightFamilyNames.optString(index, "");
michael@0 878 }
michael@0 879
michael@0 880 index++;
michael@0 881 compareResult = leftName.compareTo(rightName);
michael@0 882
michael@0 883 } while (compareResult == 0 && index < maxArrayLength);
michael@0 884
michael@0 885 // If descending order, flip the result
michael@0 886 if (compareResult != 0 && "descending".equals(mSortOrder)) {
michael@0 887 compareResult = -compareResult;
michael@0 888 }
michael@0 889
michael@0 890 return compareResult;
michael@0 891 }
michael@0 892 }
michael@0 893
michael@0 894 private void clearAllContacts(final JSONObject contactOptions, final String requestID) {
michael@0 895 ArrayList<ContentProviderOperation> deleteOptions = new ArrayList<ContentProviderOperation>();
michael@0 896
michael@0 897 // Delete all contacts from the selected account
michael@0 898 ContentProviderOperation.Builder deleteOptionsBuilder = ContentProviderOperation.newDelete(RawContacts.CONTENT_URI);
michael@0 899 if (mAccountName != null) {
michael@0 900 deleteOptionsBuilder.withSelection(RawContacts.ACCOUNT_NAME + "=?", new String[] {mAccountName})
michael@0 901 .withSelection(RawContacts.ACCOUNT_TYPE + "=?", new String[] {mAccountType});
michael@0 902 }
michael@0 903
michael@0 904 deleteOptions.add(deleteOptionsBuilder.build());
michael@0 905
michael@0 906 // Clear the contacts
michael@0 907 String returnStatus = "KO";
michael@0 908 if (applyBatch(deleteOptions) != null) {
michael@0 909 returnStatus = "OK";
michael@0 910 }
michael@0 911
michael@0 912 Log.i(LOGTAG, "Sending return status: " + returnStatus);
michael@0 913
michael@0 914 sendCallbackToJavascript("Android:Contacts:Clear:Return:" + returnStatus, requestID,
michael@0 915 new String[] {"contactID"}, new Object[] {"undefined"});
michael@0 916
michael@0 917 }
michael@0 918
michael@0 919 private boolean deleteContact(String rawContactId) {
michael@0 920 ContentProviderOperation deleteOptions = ContentProviderOperation.newDelete(RawContacts.CONTENT_URI)
michael@0 921 .withSelection(RawContacts._ID + "=?",
michael@0 922 new String[] {rawContactId})
michael@0 923 .build();
michael@0 924
michael@0 925 ArrayList<ContentProviderOperation> deleteOptionsList = new ArrayList<ContentProviderOperation>();
michael@0 926 deleteOptionsList.add(deleteOptions);
michael@0 927
michael@0 928 return checkForPositiveCountInResults(applyBatch(deleteOptionsList));
michael@0 929 }
michael@0 930
michael@0 931 private void removeContact(final JSONObject contactOptions, final String requestID) {
michael@0 932 String rawContactId;
michael@0 933 try {
michael@0 934 rawContactId = contactOptions.getString("id");
michael@0 935 Log.i(LOGTAG, "Removing contact with ID: " + rawContactId);
michael@0 936 } catch (JSONException e) {
michael@0 937 // We can't continue without a raw contact ID
michael@0 938 sendCallbackToJavascript("Android:Contact:Remove:Return:KO", requestID, null, null);
michael@0 939 return;
michael@0 940 }
michael@0 941
michael@0 942 String returnStatus = "KO";
michael@0 943 if(deleteContact(rawContactId)) {
michael@0 944 returnStatus = "OK";
michael@0 945 }
michael@0 946
michael@0 947 sendCallbackToJavascript("Android:Contact:Remove:Return:" + returnStatus, requestID,
michael@0 948 new String[] {"contactID"}, new Object[] {rawContactId});
michael@0 949 }
michael@0 950
michael@0 951 private void saveContact(final JSONObject contactOptions, final String requestID) {
michael@0 952 try {
michael@0 953 String reason = contactOptions.getString("reason");
michael@0 954 JSONObject contact = contactOptions.getJSONObject("contact");
michael@0 955 JSONObject contactProperties = contact.getJSONObject("properties");
michael@0 956
michael@0 957 if ("update".equals(reason)) {
michael@0 958 updateContact(contactProperties, contact.getLong("id"), requestID);
michael@0 959 } else {
michael@0 960 insertContact(contactProperties, requestID);
michael@0 961 }
michael@0 962 } catch (JSONException e) {
michael@0 963 throw new IllegalArgumentException(e);
michael@0 964 }
michael@0 965 }
michael@0 966
michael@0 967 private void insertContact(final JSONObject contactProperties, final String requestID) throws JSONException {
michael@0 968 ArrayList<ContentProviderOperation> newContactOptions = new ArrayList<ContentProviderOperation>();
michael@0 969
michael@0 970 // Account to save the contact under
michael@0 971 newContactOptions.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
michael@0 972 .withValue(RawContacts.ACCOUNT_NAME, mAccountName)
michael@0 973 .withValue(RawContacts.ACCOUNT_TYPE, mAccountType)
michael@0 974 .build());
michael@0 975
michael@0 976 List<ContentValues> newContactValues = getContactValues(contactProperties);
michael@0 977
michael@0 978 for (ContentValues values : newContactValues) {
michael@0 979 newContactOptions.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
michael@0 980 .withValueBackReference(Data.RAW_CONTACT_ID, 0)
michael@0 981 .withValues(values)
michael@0 982 .build());
michael@0 983 }
michael@0 984
michael@0 985 String returnStatus = "KO";
michael@0 986 Long newRawContactId = new Long(-1);
michael@0 987
michael@0 988 // Insert the contact!
michael@0 989 ContentProviderResult[] insertResults = applyBatch(newContactOptions);
michael@0 990
michael@0 991 if (insertResults != null) {
michael@0 992 try {
michael@0 993 // Get the ID of the newly created contact
michael@0 994 newRawContactId = getRawContactIdFromContentProviderResults(insertResults);
michael@0 995
michael@0 996 if (newRawContactId != null) {
michael@0 997 returnStatus = "OK";
michael@0 998 }
michael@0 999 } catch (NumberFormatException e) {
michael@0 1000 Log.e(LOGTAG, "NumberFormatException", e);
michael@0 1001 }
michael@0 1002
michael@0 1003 Log.i(LOGTAG, "Newly created contact ID: " + newRawContactId);
michael@0 1004 }
michael@0 1005
michael@0 1006 Log.i(LOGTAG, "Sending return status: " + returnStatus);
michael@0 1007
michael@0 1008 sendCallbackToJavascript("Android:Contact:Save:Return:" + returnStatus, requestID,
michael@0 1009 new String[] {"contactID", "reason"},
michael@0 1010 new Object[] {newRawContactId, "create"});
michael@0 1011 }
michael@0 1012
michael@0 1013 private void updateContact(final JSONObject contactProperties, final long rawContactId, final String requestID) throws JSONException {
michael@0 1014 // Why is updating a contact so weird and horribly inefficient? Because Android doesn't
michael@0 1015 // like multiple values for contact fields, but the Mozilla contacts API calls for this.
michael@0 1016 // This means the Android update function is essentially completely useless. Why not just
michael@0 1017 // delete the contact and re-insert it? Because that would change the contact ID and the
michael@0 1018 // Mozilla contacts API shouldn't have this behavior. The solution is to delete each
michael@0 1019 // row from the contacts data table that belongs to the contact, and insert the new
michael@0 1020 // fields. But then why not just delete all the data from the data in one go and
michael@0 1021 // insert the new data in another? Because if all the data relating to a contact is
michael@0 1022 // deleted, Android will "conviently" remove the ID making it impossible to insert data
michael@0 1023 // under the old ID. To work around this, we put a Mozilla contact flag in the database
michael@0 1024
michael@0 1025 ContentProviderOperation removeOptions = ContentProviderOperation.newDelete(Data.CONTENT_URI)
michael@0 1026 .withSelection(Data.RAW_CONTACT_ID + "=? AND " +
michael@0 1027 Data.MIMETYPE + " != '" + MIMETYPE_MOZILLA_CONTACTS_FLAG + "'",
michael@0 1028 new String[] {String.valueOf(rawContactId)})
michael@0 1029 .build();
michael@0 1030
michael@0 1031 ArrayList<ContentProviderOperation> removeOptionsList = new ArrayList<ContentProviderOperation>();
michael@0 1032 removeOptionsList.add(removeOptions);
michael@0 1033
michael@0 1034 ContentProviderResult[] removeResults = applyBatch(removeOptionsList);
michael@0 1035
michael@0 1036 // Check if the remove failed
michael@0 1037 if (removeResults == null || !checkForPositiveCountInResults(removeResults)) {
michael@0 1038 Log.w(LOGTAG, "Null or 0 remove results");
michael@0 1039
michael@0 1040 sendCallbackToJavascript("Android:Contact:Save:Return:KO", requestID, null, null);
michael@0 1041 return;
michael@0 1042 }
michael@0 1043
michael@0 1044 List<ContentValues> updateContactValues = getContactValues(contactProperties);
michael@0 1045 ArrayList<ContentProviderOperation> updateContactOptions = new ArrayList<ContentProviderOperation>();
michael@0 1046
michael@0 1047 for (ContentValues values : updateContactValues) {
michael@0 1048 updateContactOptions.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
michael@0 1049 .withValue(Data.RAW_CONTACT_ID, rawContactId)
michael@0 1050 .withValues(values)
michael@0 1051 .build());
michael@0 1052 }
michael@0 1053
michael@0 1054 String returnStatus = "KO";
michael@0 1055
michael@0 1056 // Update the contact!
michael@0 1057 applyBatch(updateContactOptions);
michael@0 1058
michael@0 1059 sendCallbackToJavascript("Android:Contact:Save:Return:OK", requestID,
michael@0 1060 new String[] {"contactID", "reason"},
michael@0 1061 new Object[] {rawContactId, "update"});
michael@0 1062 }
michael@0 1063
michael@0 1064 private List<ContentValues> getContactValues(final JSONObject contactProperties) throws JSONException {
michael@0 1065 List<ContentValues> contactValues = new ArrayList<ContentValues>();
michael@0 1066
michael@0 1067 // Add the contact to the default group so it is shown in other apps
michael@0 1068 // like the Contacts or People app
michael@0 1069 ContentValues defaultGroupValues = new ContentValues();
michael@0 1070 defaultGroupValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
michael@0 1071 defaultGroupValues.put(GroupMembership.GROUP_ROW_ID, mGroupId);
michael@0 1072 contactValues.add(defaultGroupValues);
michael@0 1073
michael@0 1074 // Create all the values that will be inserted into the new contact
michael@0 1075 getNameValues(contactProperties.optJSONArray("name"),
michael@0 1076 contactProperties.optJSONArray("givenName"),
michael@0 1077 contactProperties.optJSONArray("familyName"),
michael@0 1078 contactProperties.optJSONArray("honorificPrefix"),
michael@0 1079 contactProperties.optJSONArray("honorificSuffix"),
michael@0 1080 contactValues);
michael@0 1081
michael@0 1082 getGenericValues(MIMETYPE_ADDITIONAL_NAME, CUSTOM_DATA_COLUMN,
michael@0 1083 contactProperties.optJSONArray("additionalName"), contactValues);
michael@0 1084
michael@0 1085 getNicknamesValues(contactProperties.optJSONArray("nickname"), contactValues);
michael@0 1086
michael@0 1087 getAddressesValues(contactProperties.optJSONArray("adr"), contactValues);
michael@0 1088
michael@0 1089 getPhonesValues(contactProperties.optJSONArray("tel"), contactValues);
michael@0 1090
michael@0 1091 getEmailsValues(contactProperties.optJSONArray("email"), contactValues);
michael@0 1092
michael@0 1093 //getPhotosValues(contactProperties.optJSONArray("photo"), contactValues);
michael@0 1094
michael@0 1095 getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.COMPANY,
michael@0 1096 contactProperties.optJSONArray("org"), contactValues);
michael@0 1097
michael@0 1098 getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.TITLE,
michael@0 1099 contactProperties.optJSONArray("jobTitle"), contactValues);
michael@0 1100
michael@0 1101 getNotesValues(contactProperties.optJSONArray("note"), contactValues);
michael@0 1102
michael@0 1103 getWebsitesValues(contactProperties.optJSONArray("url"), contactValues);
michael@0 1104
michael@0 1105 getImsValues(contactProperties.optJSONArray("impp"), contactValues);
michael@0 1106
michael@0 1107 getCategoriesValues(contactProperties.optJSONArray("category"), contactValues);
michael@0 1108
michael@0 1109 getEventValues(contactProperties.optString("bday"), Event.TYPE_BIRTHDAY, contactValues);
michael@0 1110
michael@0 1111 getEventValues(contactProperties.optString("anniversary"), Event.TYPE_ANNIVERSARY, contactValues);
michael@0 1112
michael@0 1113 getCustomMimetypeValues(contactProperties.optString("sex"), MIMETYPE_SEX, contactValues);
michael@0 1114
michael@0 1115 getCustomMimetypeValues(contactProperties.optString("genderIdentity"), MIMETYPE_GENDER_IDENTITY, contactValues);
michael@0 1116
michael@0 1117 getGenericValues(MIMETYPE_KEY, CUSTOM_DATA_COLUMN, contactProperties.optJSONArray("key"),
michael@0 1118 contactValues);
michael@0 1119
michael@0 1120 return contactValues;
michael@0 1121 }
michael@0 1122
michael@0 1123 private void getGenericValues(final String mimeType, final String dataType, final JSONArray fields,
michael@0 1124 List<ContentValues> newContactValues) throws JSONException {
michael@0 1125 if (fields == null) {
michael@0 1126 return;
michael@0 1127 }
michael@0 1128
michael@0 1129 for (int i = 0; i < fields.length(); i++) {
michael@0 1130 ContentValues contentValues = new ContentValues();
michael@0 1131 contentValues.put(Data.MIMETYPE, mimeType);
michael@0 1132 contentValues.put(dataType, fields.getString(i));
michael@0 1133 newContactValues.add(contentValues);
michael@0 1134 }
michael@0 1135 }
michael@0 1136
michael@0 1137 private void getNameValues(final JSONArray displayNames, final JSONArray givenNames,
michael@0 1138 final JSONArray familyNames, final JSONArray prefixes,
michael@0 1139 final JSONArray suffixes, List<ContentValues> newContactValues) throws JSONException {
michael@0 1140 int maxLen = max((displayNames != null ? displayNames.length() : 0),
michael@0 1141 (givenNames != null ? givenNames.length() : 0),
michael@0 1142 (familyNames != null ? familyNames.length() : 0),
michael@0 1143 (prefixes != null ? prefixes.length() : 0),
michael@0 1144 (suffixes != null ? suffixes.length() : 0));
michael@0 1145
michael@0 1146 for (int i = 0; i < maxLen; i++) {
michael@0 1147 ContentValues contentValues = new ContentValues();
michael@0 1148 contentValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
michael@0 1149
michael@0 1150 final String displayName = (displayNames != null ? displayNames.optString(i, null) : null);
michael@0 1151 final String givenName = (givenNames != null ? givenNames.optString(i, null) : null);
michael@0 1152 final String familyName = (familyNames != null ? familyNames.optString(i, null) : null);
michael@0 1153 final String prefix = (prefixes != null ? prefixes.optString(i, null) : null);
michael@0 1154 final String suffix = (suffixes != null ? suffixes.optString(i, null) : null);
michael@0 1155
michael@0 1156 if (displayName != null) {
michael@0 1157 contentValues.put(StructuredName.DISPLAY_NAME, displayName);
michael@0 1158 }
michael@0 1159 if (givenName != null) {
michael@0 1160 contentValues.put(StructuredName.GIVEN_NAME, givenName);
michael@0 1161 }
michael@0 1162 if (familyName != null) {
michael@0 1163 contentValues.put(StructuredName.FAMILY_NAME, familyName);
michael@0 1164 }
michael@0 1165 if (prefix != null) {
michael@0 1166 contentValues.put(StructuredName.PREFIX, prefix);
michael@0 1167 }
michael@0 1168 if (suffix != null) {
michael@0 1169 contentValues.put(StructuredName.SUFFIX, suffix);
michael@0 1170 }
michael@0 1171
michael@0 1172 newContactValues.add(contentValues);
michael@0 1173 }
michael@0 1174 }
michael@0 1175
michael@0 1176 private void getNicknamesValues(final JSONArray nicknames, List<ContentValues> newContactValues) throws JSONException {
michael@0 1177 if (nicknames == null) {
michael@0 1178 return;
michael@0 1179 }
michael@0 1180
michael@0 1181 for (int i = 0; i < nicknames.length(); i++) {
michael@0 1182 ContentValues contentValues = new ContentValues();
michael@0 1183 contentValues.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
michael@0 1184 contentValues.put(Nickname.NAME, nicknames.getString(i));
michael@0 1185 contentValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT);
michael@0 1186 newContactValues.add(contentValues);
michael@0 1187 }
michael@0 1188 }
michael@0 1189
michael@0 1190 private void getAddressesValues(final JSONArray addresses, List<ContentValues> newContactValues) throws JSONException {
michael@0 1191 if (addresses == null) {
michael@0 1192 return;
michael@0 1193 }
michael@0 1194
michael@0 1195 for (int i = 0; i < addresses.length(); i++) {
michael@0 1196 JSONObject address = addresses.getJSONObject(i);
michael@0 1197 JSONArray addressTypes = address.optJSONArray("type");
michael@0 1198
michael@0 1199 if (addressTypes != null) {
michael@0 1200 for (int j = 0; j < addressTypes.length(); j++) {
michael@0 1201 // Translate the address type string to an integer constant
michael@0 1202 // provided by the ContactsContract API
michael@0 1203 final String type = addressTypes.getString(j);
michael@0 1204 final int typeConstant = getAddressType(type);
michael@0 1205
michael@0 1206 newContactValues.add(createAddressContentValues(address, typeConstant, type));
michael@0 1207 }
michael@0 1208 } else {
michael@0 1209 newContactValues.add(createAddressContentValues(address, -1, null));
michael@0 1210 }
michael@0 1211 }
michael@0 1212 }
michael@0 1213
michael@0 1214 private ContentValues createAddressContentValues(final JSONObject address, final int typeConstant,
michael@0 1215 final String type) throws JSONException {
michael@0 1216 ContentValues contentValues = new ContentValues();
michael@0 1217 contentValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
michael@0 1218 contentValues.put(StructuredPostal.STREET, address.optString("streetAddress"));
michael@0 1219 contentValues.put(StructuredPostal.CITY, address.optString("locality"));
michael@0 1220 contentValues.put(StructuredPostal.REGION, address.optString("region"));
michael@0 1221 contentValues.put(StructuredPostal.POSTCODE, address.optString("postalCode"));
michael@0 1222 contentValues.put(StructuredPostal.COUNTRY, address.optString("countryName"));
michael@0 1223
michael@0 1224 if (type != null) {
michael@0 1225 contentValues.put(StructuredPostal.TYPE, typeConstant);
michael@0 1226
michael@0 1227 // If a custom type, add a label
michael@0 1228 if (typeConstant == BaseTypes.TYPE_CUSTOM) {
michael@0 1229 contentValues.put(StructuredPostal.LABEL, type);
michael@0 1230 }
michael@0 1231 }
michael@0 1232
michael@0 1233 if (address.has("pref")) {
michael@0 1234 contentValues.put(Data.IS_SUPER_PRIMARY, address.getBoolean("pref") ? 1 : 0);
michael@0 1235 }
michael@0 1236
michael@0 1237 return contentValues;
michael@0 1238 }
michael@0 1239
michael@0 1240 private void getPhonesValues(final JSONArray phones, List<ContentValues> newContactValues) throws JSONException {
michael@0 1241 if (phones == null) {
michael@0 1242 return;
michael@0 1243 }
michael@0 1244
michael@0 1245 for (int i = 0; i < phones.length(); i++) {
michael@0 1246 JSONObject phone = phones.getJSONObject(i);
michael@0 1247 JSONArray phoneTypes = phone.optJSONArray("type");
michael@0 1248 ContentValues contentValues;
michael@0 1249
michael@0 1250 if (phoneTypes != null && phoneTypes.length() > 0) {
michael@0 1251 for (int j = 0; j < phoneTypes.length(); j++) {
michael@0 1252 // Translate the phone type string to an integer constant
michael@0 1253 // provided by the ContactsContract API
michael@0 1254 final String type = phoneTypes.getString(j);
michael@0 1255 final int typeConstant = getPhoneType(type);
michael@0 1256
michael@0 1257 contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"),
michael@0 1258 typeConstant, type, phone.optBoolean("pref"));
michael@0 1259 if (phone.has("carrier")) {
michael@0 1260 contentValues.put(CARRIER_COLUMN, phone.optString("carrier"));
michael@0 1261 }
michael@0 1262 newContactValues.add(contentValues);
michael@0 1263 }
michael@0 1264 } else {
michael@0 1265 contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"),
michael@0 1266 -1, null, phone.optBoolean("pref"));
michael@0 1267 if (phone.has("carrier")) {
michael@0 1268 contentValues.put(CARRIER_COLUMN, phone.optString("carrier"));
michael@0 1269 }
michael@0 1270 newContactValues.add(contentValues);
michael@0 1271 }
michael@0 1272 }
michael@0 1273 }
michael@0 1274
michael@0 1275 private void getEmailsValues(final JSONArray emails, List<ContentValues> newContactValues) throws JSONException {
michael@0 1276 if (emails == null) {
michael@0 1277 return;
michael@0 1278 }
michael@0 1279
michael@0 1280 for (int i = 0; i < emails.length(); i++) {
michael@0 1281 JSONObject email = emails.getJSONObject(i);
michael@0 1282 JSONArray emailTypes = email.optJSONArray("type");
michael@0 1283
michael@0 1284 if (emailTypes != null && emailTypes.length() > 0) {
michael@0 1285 for (int j = 0; j < emailTypes.length(); j++) {
michael@0 1286 // Translate the email type string to an integer constant
michael@0 1287 // provided by the ContactsContract API
michael@0 1288 final String type = emailTypes.getString(j);
michael@0 1289 final int typeConstant = getEmailType(type);
michael@0 1290
michael@0 1291 newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE,
michael@0 1292 email.optString("value"),
michael@0 1293 typeConstant, type,
michael@0 1294 email.optBoolean("pref")));
michael@0 1295 }
michael@0 1296 } else {
michael@0 1297 newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE,
michael@0 1298 email.optString("value"),
michael@0 1299 -1, null, email.optBoolean("pref")));
michael@0 1300 }
michael@0 1301 }
michael@0 1302 }
michael@0 1303
michael@0 1304 private void getPhotosValues(final JSONArray photos, List<ContentValues> newContactValues) throws JSONException {
michael@0 1305 if (photos == null) {
michael@0 1306 return;
michael@0 1307 }
michael@0 1308
michael@0 1309 // TODO: implement this
michael@0 1310 }
michael@0 1311
michael@0 1312 private void getNotesValues(final JSONArray notes, List<ContentValues> newContactValues) throws JSONException {
michael@0 1313 if (notes == null) {
michael@0 1314 return;
michael@0 1315 }
michael@0 1316
michael@0 1317 for (int i = 0; i < notes.length(); i++) {
michael@0 1318 ContentValues contentValues = new ContentValues();
michael@0 1319 contentValues.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
michael@0 1320 contentValues.put(Note.NOTE, notes.getString(i));
michael@0 1321 newContactValues.add(contentValues);
michael@0 1322 }
michael@0 1323 }
michael@0 1324
michael@0 1325 private void getWebsitesValues(final JSONArray websites, List<ContentValues> newContactValues) throws JSONException {
michael@0 1326 if (websites == null) {
michael@0 1327 return;
michael@0 1328 }
michael@0 1329
michael@0 1330 for (int i = 0; i < websites.length(); i++) {
michael@0 1331 JSONObject website = websites.getJSONObject(i);
michael@0 1332 JSONArray websiteTypes = website.optJSONArray("type");
michael@0 1333
michael@0 1334 if (websiteTypes != null && websiteTypes.length() > 0) {
michael@0 1335 for (int j = 0; j < websiteTypes.length(); j++) {
michael@0 1336 // Translate the website type string to an integer constant
michael@0 1337 // provided by the ContactsContract API
michael@0 1338 final String type = websiteTypes.getString(j);
michael@0 1339 final int typeConstant = getWebsiteType(type);
michael@0 1340
michael@0 1341 newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE,
michael@0 1342 website.optString("value"),
michael@0 1343 typeConstant, type,
michael@0 1344 website.optBoolean("pref")));
michael@0 1345 }
michael@0 1346 } else {
michael@0 1347 newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE,
michael@0 1348 website.optString("value"),
michael@0 1349 -1, null, website.optBoolean("pref")));
michael@0 1350 }
michael@0 1351 }
michael@0 1352 }
michael@0 1353
michael@0 1354 private void getImsValues(final JSONArray ims, List<ContentValues> newContactValues) throws JSONException {
michael@0 1355 if (ims == null) {
michael@0 1356 return;
michael@0 1357 }
michael@0 1358
michael@0 1359 for (int i = 0; i < ims.length(); i++) {
michael@0 1360 JSONObject im = ims.getJSONObject(i);
michael@0 1361 JSONArray imTypes = im.optJSONArray("type");
michael@0 1362
michael@0 1363 if (imTypes != null && imTypes.length() > 0) {
michael@0 1364 for (int j = 0; j < imTypes.length(); j++) {
michael@0 1365 // Translate the IM type string to an integer constant
michael@0 1366 // provided by the ContactsContract API
michael@0 1367 final String type = imTypes.getString(j);
michael@0 1368 final int typeConstant = getImType(type);
michael@0 1369
michael@0 1370 newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE,
michael@0 1371 im.optString("value"),
michael@0 1372 typeConstant, type,
michael@0 1373 im.optBoolean("pref")));
michael@0 1374 }
michael@0 1375 } else {
michael@0 1376 newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE,
michael@0 1377 im.optString("value"),
michael@0 1378 -1, null, im.optBoolean("pref")));
michael@0 1379 }
michael@0 1380 }
michael@0 1381 }
michael@0 1382
michael@0 1383 private void getCategoriesValues(final JSONArray categories, List<ContentValues> newContactValues) throws JSONException {
michael@0 1384 if (categories == null) {
michael@0 1385 return;
michael@0 1386 }
michael@0 1387
michael@0 1388 for (int i = 0; i < categories.length(); i++) {
michael@0 1389 String category = categories.getString(i);
michael@0 1390
michael@0 1391 if ("my contacts".equals(category.toLowerCase()) ||
michael@0 1392 PRE_HONEYCOMB_DEFAULT_GROUP.equalsIgnoreCase(category)) {
michael@0 1393 Log.w(LOGTAG, "New contacts are implicitly added to the default group.");
michael@0 1394 continue;
michael@0 1395 }
michael@0 1396
michael@0 1397 // Find the group ID of the given category
michael@0 1398 long groupId = getGroupId(category);
michael@0 1399
michael@0 1400 // Create the group if it doesn't already exist
michael@0 1401 if (groupId == -1) {
michael@0 1402 groupId = createGroup(category);
michael@0 1403 // If the group is still -1, we failed to create the group
michael@0 1404 if (groupId == -1) {
michael@0 1405 // Only log the category name if in debug
michael@0 1406 if (DEBUG) {
michael@0 1407 Log.d(LOGTAG, "Failed to create new group for category \"" + category + "\"");
michael@0 1408 } else {
michael@0 1409 Log.w(LOGTAG, "Failed to create new group for given category.");
michael@0 1410 }
michael@0 1411 continue;
michael@0 1412 }
michael@0 1413 }
michael@0 1414
michael@0 1415 ContentValues contentValues = new ContentValues();
michael@0 1416 contentValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
michael@0 1417 contentValues.put(GroupMembership.GROUP_ROW_ID, groupId);
michael@0 1418 newContactValues.add(contentValues);
michael@0 1419
michael@0 1420 newContactValues.add(contentValues);
michael@0 1421 }
michael@0 1422 }
michael@0 1423
michael@0 1424 private void getEventValues(final String event, final int type, List<ContentValues> newContactValues) {
michael@0 1425 if (event == null || event.length() < 11) {
michael@0 1426 return;
michael@0 1427 }
michael@0 1428
michael@0 1429 ContentValues contentValues = new ContentValues();
michael@0 1430 contentValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
michael@0 1431 contentValues.put(Event.START_DATE, event.substring(0, 10));
michael@0 1432 contentValues.put(Event.TYPE, type);
michael@0 1433 newContactValues.add(contentValues);
michael@0 1434 }
michael@0 1435
michael@0 1436 private void getCustomMimetypeValues(final String value, final String mimeType, List<ContentValues> newContactValues) {
michael@0 1437 if (value == null || "null".equals(value)) {
michael@0 1438 return;
michael@0 1439 }
michael@0 1440
michael@0 1441 ContentValues contentValues = new ContentValues();
michael@0 1442 contentValues.put(Data.MIMETYPE, mimeType);
michael@0 1443 contentValues.put(CUSTOM_DATA_COLUMN, value);
michael@0 1444 newContactValues.add(contentValues);
michael@0 1445 }
michael@0 1446
michael@0 1447 private void getMozillaContactFlagValues(List<ContentValues> newContactValues) {
michael@0 1448 try {
michael@0 1449 JSONArray mozillaContactsFlag = new JSONArray();
michael@0 1450 mozillaContactsFlag.put("1");
michael@0 1451 getGenericValues(MIMETYPE_MOZILLA_CONTACTS_FLAG, CUSTOM_DATA_COLUMN, mozillaContactsFlag, newContactValues);
michael@0 1452 } catch (JSONException e) {
michael@0 1453 throw new IllegalArgumentException(e);
michael@0 1454 }
michael@0 1455 }
michael@0 1456
michael@0 1457 private ContentValues createContentValues(final String mimeType, final String value, final int typeConstant,
michael@0 1458 final String type, final boolean preferredValue) {
michael@0 1459 ContentValues contentValues = new ContentValues();
michael@0 1460 contentValues.put(Data.MIMETYPE, mimeType);
michael@0 1461 contentValues.put(Data.DATA1, value);
michael@0 1462 contentValues.put(Data.IS_SUPER_PRIMARY, preferredValue ? 1 : 0);
michael@0 1463
michael@0 1464 if (type != null) {
michael@0 1465 contentValues.put(Data.DATA2, typeConstant);
michael@0 1466
michael@0 1467 // If a custom type, add a label
michael@0 1468 if (typeConstant == BaseTypes.TYPE_CUSTOM) {
michael@0 1469 contentValues.put(Data.DATA3, type);
michael@0 1470 }
michael@0 1471 }
michael@0 1472
michael@0 1473 return contentValues;
michael@0 1474 }
michael@0 1475
michael@0 1476 private void getContactsCount(final String requestID) {
michael@0 1477 Cursor cursor = getAllRawContactIdsCursor();
michael@0 1478 Integer numContacts = Integer.valueOf(cursor.getCount());
michael@0 1479 cursor.close();
michael@0 1480
michael@0 1481 sendCallbackToJavascript("Android:Contacts:Count", requestID, new String[] {"count"},
michael@0 1482 new Object[] {numContacts});
michael@0 1483 }
michael@0 1484
michael@0 1485 private void sendCallbackToJavascript(final String subject, final String requestID,
michael@0 1486 final String[] argNames, final Object[] argValues) {
michael@0 1487 // Check the same number of argument names and arguments were given
michael@0 1488 if (argNames != null && argNames.length != argValues.length) {
michael@0 1489 throw new IllegalArgumentException("Argument names and argument values lengths do not match. " +
michael@0 1490 "Names length = " + argNames.length + ", Values length = " +
michael@0 1491 argValues.length);
michael@0 1492 }
michael@0 1493
michael@0 1494 try {
michael@0 1495 JSONObject callbackMessage = new JSONObject();
michael@0 1496 callbackMessage.put("requestID", requestID);
michael@0 1497
michael@0 1498 if (argNames != null) {
michael@0 1499 for (int i = 0; i < argNames.length; i++) {
michael@0 1500 callbackMessage.put(argNames[i], argValues[i]);
michael@0 1501 }
michael@0 1502 }
michael@0 1503
michael@0 1504 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(subject, callbackMessage.toString()));
michael@0 1505 } catch (JSONException e) {
michael@0 1506 throw new IllegalArgumentException(e);
michael@0 1507 }
michael@0 1508 }
michael@0 1509
michael@0 1510 private void registerEventListener(final String event) {
michael@0 1511 mEventDispatcher.registerEventListener(event, this);
michael@0 1512 }
michael@0 1513
michael@0 1514 private void unregisterEventListener(final String event) {
michael@0 1515 mEventDispatcher.unregisterEventListener(event, this);
michael@0 1516 }
michael@0 1517
michael@0 1518 private ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
michael@0 1519 try {
michael@0 1520 return mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
michael@0 1521 } catch (RemoteException e) {
michael@0 1522 Log.e(LOGTAG, "RemoteException", e);
michael@0 1523 } catch (OperationApplicationException e) {
michael@0 1524 Log.e(LOGTAG, "OperationApplicationException", e);
michael@0 1525 }
michael@0 1526 return null;
michael@0 1527 }
michael@0 1528
michael@0 1529 private void getDeviceAccount(final Runnable handleMessage) {
michael@0 1530 Account[] accounts = AccountManager.get(mActivity).getAccounts();
michael@0 1531
michael@0 1532 if (accounts.length == 0) {
michael@0 1533 Log.w(LOGTAG, "No accounts available");
michael@0 1534 gotDeviceAccount(handleMessage);
michael@0 1535 } else if (accounts.length > 1) {
michael@0 1536 // Show the accounts chooser dialog if more than one dialog exists
michael@0 1537 showAccountsDialog(accounts, handleMessage);
michael@0 1538 } else {
michael@0 1539 // If only one account exists, use it
michael@0 1540 mAccountName = accounts[0].name;
michael@0 1541 mAccountType = accounts[0].type;
michael@0 1542 gotDeviceAccount(handleMessage);
michael@0 1543 }
michael@0 1544
michael@0 1545 mGotDeviceAccount = true;
michael@0 1546 }
michael@0 1547
michael@0 1548 private void showAccountsDialog(final Account[] accounts, final Runnable handleMessage) {
michael@0 1549 String[] accountNames = new String[accounts.length];
michael@0 1550 for (int i = 0; i < accounts.length; i++) {
michael@0 1551 accountNames[i] = accounts[i].name;
michael@0 1552 }
michael@0 1553
michael@0 1554 final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
michael@0 1555 builder.setTitle(mActivity.getResources().getString(R.string.contacts_account_chooser_dialog_title))
michael@0 1556 .setSingleChoiceItems(accountNames, 0, new DialogInterface.OnClickListener() {
michael@0 1557 @Override
michael@0 1558 public void onClick(DialogInterface dialog, int position) {
michael@0 1559 // Set the account name and type when an item is selected and dismiss the dialog
michael@0 1560 mAccountName = accounts[position].name;
michael@0 1561 mAccountType = accounts[position].type;
michael@0 1562 dialog.dismiss();
michael@0 1563 gotDeviceAccount(handleMessage);
michael@0 1564 }
michael@0 1565 });
michael@0 1566
michael@0 1567 mActivity.runOnUiThread(new Runnable() {
michael@0 1568 public void run() {
michael@0 1569 builder.show();
michael@0 1570 }
michael@0 1571 });
michael@0 1572 }
michael@0 1573
michael@0 1574 private void gotDeviceAccount(final Runnable handleMessage) {
michael@0 1575 // Force the handleMessage runnable and getDefaultGroupId to run on the background thread
michael@0 1576 Runnable runnable = new Runnable() {
michael@0 1577 @Override
michael@0 1578 public void run() {
michael@0 1579 getDefaultGroupId();
michael@0 1580
michael@0 1581 // Don't log a user's account if not debug mode. Otherwise, just log a message
michael@0 1582 // saying that we got an account to use
michael@0 1583 if (mAccountName == null) {
michael@0 1584 Log.i(LOGTAG, "No device account selected. Leaving account as null.");
michael@0 1585 } else if (DEBUG) {
michael@0 1586 Log.d(LOGTAG, "Using account: " + mAccountName + " (type: " + mAccountType + ")");
michael@0 1587 } else {
michael@0 1588 Log.i(LOGTAG, "Got device account to use for contact operations.");
michael@0 1589 }
michael@0 1590 handleMessage.run();
michael@0 1591 }
michael@0 1592 };
michael@0 1593
michael@0 1594 ThreadUtils.postToBackgroundThread(runnable);
michael@0 1595 }
michael@0 1596
michael@0 1597 private void getDefaultGroupId() {
michael@0 1598 Cursor cursor = getAllGroups();
michael@0 1599
michael@0 1600 cursor.moveToPosition(-1);
michael@0 1601 while (cursor.moveToNext()) {
michael@0 1602 // Check if the account name and type for the group match the account name and type of
michael@0 1603 // the account we're working with
michael@0 1604 final String groupAccountName = cursor.getString(GROUP_ACCOUNT_NAME);
michael@0 1605 if (!groupAccountName.equals(mAccountName)) {
michael@0 1606 continue;
michael@0 1607 }
michael@0 1608
michael@0 1609 final String groupAccountType = cursor.getString(GROUP_ACCOUNT_TYPE);
michael@0 1610 if (!groupAccountType.equals(mAccountType)) {
michael@0 1611 continue;
michael@0 1612 }
michael@0 1613
michael@0 1614 // For all honeycomb and up, the default group is the first one which has the AUTO_ADD flag set
michael@0 1615 if (isAutoAddGroup(cursor)) {
michael@0 1616 mGroupTitle = cursor.getString(GROUP_TITLE);
michael@0 1617 mGroupId = cursor.getLong(GROUP_ID);
michael@0 1618 break;
michael@0 1619 } else if (PRE_HONEYCOMB_DEFAULT_GROUP.equals(cursor.getString(GROUP_TITLE))) {
michael@0 1620 mGroupId = cursor.getLong(GROUP_ID);
michael@0 1621 mGroupTitle = PRE_HONEYCOMB_DEFAULT_GROUP;
michael@0 1622 break;
michael@0 1623 }
michael@0 1624 }
michael@0 1625 cursor.close();
michael@0 1626
michael@0 1627 if (mGroupId == 0) {
michael@0 1628 Log.w(LOGTAG, "Default group ID not found. Newly created contacts will not belong to any groups.");
michael@0 1629 } else if (DEBUG) {
michael@0 1630 Log.i(LOGTAG, "Using group ID: " + mGroupId + " (" + mGroupTitle + ")");
michael@0 1631 }
michael@0 1632 }
michael@0 1633
michael@0 1634 private static boolean isAutoAddGroup(Cursor cursor) {
michael@0 1635 // For Honeycomb and up, the default group is the first one which has the AUTO_ADD flag set.
michael@0 1636 // For everything below Honeycomb, use the default "System Group: My Contacts" group
michael@0 1637 return (Build.VERSION.SDK_INT >= 11 && !cursor.isNull(GROUP_AUTO_ADD) &&
michael@0 1638 cursor.getInt(GROUP_AUTO_ADD) != 0);
michael@0 1639 }
michael@0 1640
michael@0 1641 private long getGroupId(String groupName) {
michael@0 1642 long groupId = -1;
michael@0 1643 Cursor cursor = getGroups(Groups.TITLE + " = '" + groupName + "'");
michael@0 1644
michael@0 1645 cursor.moveToPosition(-1);
michael@0 1646 while (cursor.moveToNext()) {
michael@0 1647 String groupAccountName = cursor.getString(GROUP_ACCOUNT_NAME);
michael@0 1648 String groupAccountType = cursor.getString(GROUP_ACCOUNT_TYPE);
michael@0 1649
michael@0 1650 // Check if the account name and type for the group match the account name and type of
michael@0 1651 // the account we're working with or the default "Phone" account if no account was found
michael@0 1652 if (groupAccountName.equals(mAccountName) && groupAccountType.equals(mAccountType) ||
michael@0 1653 (mAccountName == null && "Phone".equals(groupAccountType))) {
michael@0 1654 if (groupName.equals(cursor.getString(GROUP_TITLE))) {
michael@0 1655 groupId = cursor.getLong(GROUP_ID);
michael@0 1656 break;
michael@0 1657 }
michael@0 1658 }
michael@0 1659 }
michael@0 1660 cursor.close();
michael@0 1661
michael@0 1662 return groupId;
michael@0 1663 }
michael@0 1664
michael@0 1665 private String getGroupName(long groupId) {
michael@0 1666 Cursor cursor = getGroups(Groups._ID + " = " + groupId);
michael@0 1667
michael@0 1668 if (cursor.getCount() == 0) {
michael@0 1669 cursor.close();
michael@0 1670 return null;
michael@0 1671 }
michael@0 1672
michael@0 1673 cursor.moveToPosition(0);
michael@0 1674 String groupName = cursor.getString(cursor.getColumnIndex(Groups.TITLE));
michael@0 1675 cursor.close();
michael@0 1676
michael@0 1677 return groupName;
michael@0 1678 }
michael@0 1679
michael@0 1680 private Cursor getAllGroups() {
michael@0 1681 return getGroups(null);
michael@0 1682 }
michael@0 1683
michael@0 1684 private Cursor getGroups(String selectArg) {
michael@0 1685 String[] columns = new String[] {
michael@0 1686 Groups.ACCOUNT_NAME,
michael@0 1687 Groups.ACCOUNT_TYPE,
michael@0 1688 Groups._ID,
michael@0 1689 Groups.TITLE,
michael@0 1690 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Groups.AUTO_ADD : Groups._ID)
michael@0 1691 };
michael@0 1692
michael@0 1693 if (selectArg != null) {
michael@0 1694 selectArg = "AND " + selectArg;
michael@0 1695 } else {
michael@0 1696 selectArg = "";
michael@0 1697 }
michael@0 1698
michael@0 1699 return mContentResolver.query(Groups.CONTENT_URI, columns,
michael@0 1700 Groups.ACCOUNT_TYPE + " NOT NULL AND " +
michael@0 1701 Groups.ACCOUNT_NAME + " NOT NULL " + selectArg, null, null);
michael@0 1702 }
michael@0 1703
michael@0 1704 private long createGroup(String groupName) {
michael@0 1705 if (DEBUG) {
michael@0 1706 Log.d(LOGTAG, "Creating group: " + groupName);
michael@0 1707 }
michael@0 1708
michael@0 1709 ArrayList<ContentProviderOperation> newGroupOptions = new ArrayList<ContentProviderOperation>();
michael@0 1710
michael@0 1711 // Create the group under the account we're using
michael@0 1712 // If no account is selected, use a default account name/type for the group
michael@0 1713 newGroupOptions.add(ContentProviderOperation.newInsert(Groups.CONTENT_URI)
michael@0 1714 .withValue(Groups.ACCOUNT_NAME, (mAccountName == null ? "Phone" : mAccountName))
michael@0 1715 .withValue(Groups.ACCOUNT_TYPE, (mAccountType == null ? "Phone" : mAccountType))
michael@0 1716 .withValue(Groups.TITLE, groupName)
michael@0 1717 .withValue(Groups.GROUP_VISIBLE, true)
michael@0 1718 .build());
michael@0 1719
michael@0 1720 applyBatch(newGroupOptions);
michael@0 1721
michael@0 1722 // Return the ID of the newly created group
michael@0 1723 return getGroupId(groupName);
michael@0 1724 }
michael@0 1725
michael@0 1726 private long[] getAllRawContactIds() {
michael@0 1727 Cursor cursor = getAllRawContactIdsCursor();
michael@0 1728
michael@0 1729 // Put the ids into an array
michael@0 1730 long[] ids = new long[cursor.getCount()];
michael@0 1731 int index = 0;
michael@0 1732 cursor.moveToPosition(-1);
michael@0 1733 while(cursor.moveToNext()) {
michael@0 1734 ids[index] = cursor.getLong(cursor.getColumnIndex(RawContacts._ID));
michael@0 1735 index++;
michael@0 1736 }
michael@0 1737 cursor.close();
michael@0 1738
michael@0 1739 return ids;
michael@0 1740 }
michael@0 1741
michael@0 1742 private Cursor getAllRawContactIdsCursor() {
michael@0 1743 // When a contact is deleted, it actually just sets the deleted field to 1 until the
michael@0 1744 // sync adapter actually deletes the contact later so ignore any contacts with the deleted
michael@0 1745 // flag set
michael@0 1746 String selection = RawContacts.DELETED + "=0";
michael@0 1747 String[] selectionArgs = null;
michael@0 1748
michael@0 1749 // Only get contacts from the selected account
michael@0 1750 if (mAccountName != null) {
michael@0 1751 selection += " AND " + RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?";
michael@0 1752 selectionArgs = new String[] {mAccountName, mAccountType};
michael@0 1753 }
michael@0 1754
michael@0 1755 // Get the ID's of all contacts and use the number of contact ID's as
michael@0 1756 // the total number of contacts
michael@0 1757 return mContentResolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
michael@0 1758 selection, selectionArgs, null);
michael@0 1759 }
michael@0 1760
michael@0 1761 private static Long getRawContactIdFromContentProviderResults(ContentProviderResult[] results) throws NumberFormatException {
michael@0 1762 for (int i = 0; i < results.length; i++) {
michael@0 1763 if (results[i].uri == null) {
michael@0 1764 continue;
michael@0 1765 }
michael@0 1766
michael@0 1767 String uri = results[i].uri.toString();
michael@0 1768 // Check if the uri is from the raw contacts table
michael@0 1769 if (uri.contains("raw_contacts")) {
michael@0 1770 // The ID is the after the final forward slash in the URI
michael@0 1771 return Long.parseLong(uri.substring(uri.lastIndexOf("/") + 1));
michael@0 1772 }
michael@0 1773 }
michael@0 1774
michael@0 1775 return null;
michael@0 1776 }
michael@0 1777
michael@0 1778 private static boolean checkForPositiveCountInResults(ContentProviderResult[] results) {
michael@0 1779 for (int i = 0; i < results.length; i++) {
michael@0 1780 Integer count = results[i].count;
michael@0 1781
michael@0 1782 if (DEBUG) {
michael@0 1783 Log.d(LOGTAG, "Results count: " + count);
michael@0 1784 }
michael@0 1785
michael@0 1786 if (count != null && count > 0) {
michael@0 1787 return true;
michael@0 1788 }
michael@0 1789 }
michael@0 1790
michael@0 1791 return false;
michael@0 1792 }
michael@0 1793
michael@0 1794 private static long[] convertLongListToArray(List<Long> list) {
michael@0 1795 long[] array = new long[list.size()];
michael@0 1796
michael@0 1797 for (int i = 0; i < list.size(); i++) {
michael@0 1798 array[i] = list.get(i);
michael@0 1799 }
michael@0 1800
michael@0 1801 return array;
michael@0 1802 }
michael@0 1803
michael@0 1804 private static boolean doesJSONArrayContainString(final JSONArray array, final String value) {
michael@0 1805 for (int i = 0; i < array.length(); i++) {
michael@0 1806 if (value.equals(array.optString(i))) {
michael@0 1807 return true;
michael@0 1808 }
michael@0 1809 }
michael@0 1810
michael@0 1811 return false;
michael@0 1812 }
michael@0 1813
michael@0 1814 private static int max(int... values) {
michael@0 1815 int max = values[0];
michael@0 1816 for (int value : values) {
michael@0 1817 if (value > max) {
michael@0 1818 max = value;
michael@0 1819 }
michael@0 1820 }
michael@0 1821 return max;
michael@0 1822 }
michael@0 1823
michael@0 1824 private static void putPossibleNullValueInJSONObject(final String key, final Object value, JSONObject jsonObject) throws JSONException{
michael@0 1825 if (value != null) {
michael@0 1826 jsonObject.put(key, value);
michael@0 1827 } else {
michael@0 1828 jsonObject.put(key, JSONObject.NULL);
michael@0 1829 }
michael@0 1830 }
michael@0 1831
michael@0 1832 private static String getKeyFromMapValue(final HashMap<String, Integer> map, Integer value) {
michael@0 1833 for (Entry<String, Integer> entry : map.entrySet()) {
michael@0 1834 if (value == entry.getValue()) {
michael@0 1835 return entry.getKey();
michael@0 1836 }
michael@0 1837 }
michael@0 1838 return null;
michael@0 1839 }
michael@0 1840
michael@0 1841 private String getColumnNameConstant(String field) {
michael@0 1842 initColumnNameConstantsMap();
michael@0 1843 return mColumnNameConstantsMap.get(field.toLowerCase());
michael@0 1844 }
michael@0 1845
michael@0 1846 private void initColumnNameConstantsMap() {
michael@0 1847 if (mColumnNameConstantsMap != null) {
michael@0 1848 return;
michael@0 1849 }
michael@0 1850 mColumnNameConstantsMap = new HashMap<String, String>();
michael@0 1851
michael@0 1852 mColumnNameConstantsMap.put("name", StructuredName.DISPLAY_NAME);
michael@0 1853 mColumnNameConstantsMap.put("givenname", StructuredName.GIVEN_NAME);
michael@0 1854 mColumnNameConstantsMap.put("familyname", StructuredName.FAMILY_NAME);
michael@0 1855 mColumnNameConstantsMap.put("honorificprefix", StructuredName.PREFIX);
michael@0 1856 mColumnNameConstantsMap.put("honorificsuffix", StructuredName.SUFFIX);
michael@0 1857 mColumnNameConstantsMap.put("additionalname", CUSTOM_DATA_COLUMN);
michael@0 1858 mColumnNameConstantsMap.put("nickname", Nickname.NAME);
michael@0 1859 mColumnNameConstantsMap.put("adr", StructuredPostal.STREET);
michael@0 1860 mColumnNameConstantsMap.put("email", Email.ADDRESS);
michael@0 1861 mColumnNameConstantsMap.put("url", Website.URL);
michael@0 1862 mColumnNameConstantsMap.put("category", GroupMembership.GROUP_ROW_ID);
michael@0 1863 mColumnNameConstantsMap.put("tel", Phone.NUMBER);
michael@0 1864 mColumnNameConstantsMap.put("org", Organization.COMPANY);
michael@0 1865 mColumnNameConstantsMap.put("jobTitle", Organization.TITLE);
michael@0 1866 mColumnNameConstantsMap.put("note", Note.NOTE);
michael@0 1867 mColumnNameConstantsMap.put("impp", Im.DATA);
michael@0 1868 mColumnNameConstantsMap.put("sex", CUSTOM_DATA_COLUMN);
michael@0 1869 mColumnNameConstantsMap.put("genderidentity", CUSTOM_DATA_COLUMN);
michael@0 1870 mColumnNameConstantsMap.put("key", CUSTOM_DATA_COLUMN);
michael@0 1871 }
michael@0 1872
michael@0 1873 private String getMimeTypeOfField(String field) {
michael@0 1874 initMimeTypeConstantsMap();
michael@0 1875 return mMimeTypeConstantsMap.get(field.toLowerCase());
michael@0 1876 }
michael@0 1877
michael@0 1878 private void initMimeTypeConstantsMap() {
michael@0 1879 if (mMimeTypeConstantsMap != null) {
michael@0 1880 return;
michael@0 1881 }
michael@0 1882 mMimeTypeConstantsMap = new HashMap<String, String>();
michael@0 1883
michael@0 1884 mMimeTypeConstantsMap.put("name", StructuredName.CONTENT_ITEM_TYPE);
michael@0 1885 mMimeTypeConstantsMap.put("givenname", StructuredName.CONTENT_ITEM_TYPE);
michael@0 1886 mMimeTypeConstantsMap.put("familyname", StructuredName.CONTENT_ITEM_TYPE);
michael@0 1887 mMimeTypeConstantsMap.put("honorificprefix", StructuredName.CONTENT_ITEM_TYPE);
michael@0 1888 mMimeTypeConstantsMap.put("honorificsuffix", StructuredName.CONTENT_ITEM_TYPE);
michael@0 1889 mMimeTypeConstantsMap.put("additionalname", MIMETYPE_ADDITIONAL_NAME);
michael@0 1890 mMimeTypeConstantsMap.put("nickname", Nickname.CONTENT_ITEM_TYPE);
michael@0 1891 mMimeTypeConstantsMap.put("email", Email.CONTENT_ITEM_TYPE);
michael@0 1892 mMimeTypeConstantsMap.put("url", Website.CONTENT_ITEM_TYPE);
michael@0 1893 mMimeTypeConstantsMap.put("category", GroupMembership.CONTENT_ITEM_TYPE);
michael@0 1894 mMimeTypeConstantsMap.put("tel", Phone.CONTENT_ITEM_TYPE);
michael@0 1895 mMimeTypeConstantsMap.put("org", Organization.CONTENT_ITEM_TYPE);
michael@0 1896 mMimeTypeConstantsMap.put("jobTitle", Organization.CONTENT_ITEM_TYPE);
michael@0 1897 mMimeTypeConstantsMap.put("note", Note.CONTENT_ITEM_TYPE);
michael@0 1898 mMimeTypeConstantsMap.put("impp", Im.CONTENT_ITEM_TYPE);
michael@0 1899 mMimeTypeConstantsMap.put("sex", MIMETYPE_SEX);
michael@0 1900 mMimeTypeConstantsMap.put("genderidentity", MIMETYPE_GENDER_IDENTITY);
michael@0 1901 mMimeTypeConstantsMap.put("key", MIMETYPE_KEY);
michael@0 1902 }
michael@0 1903
michael@0 1904 private int getAddressType(String addressType) {
michael@0 1905 initAddressTypesMap();
michael@0 1906 Integer type = mAddressTypesMap.get(addressType.toLowerCase());
michael@0 1907 return (type != null ? Integer.valueOf(type) : StructuredPostal.TYPE_CUSTOM);
michael@0 1908 }
michael@0 1909
michael@0 1910 private void initAddressTypesMap() {
michael@0 1911 if (mAddressTypesMap != null) {
michael@0 1912 return;
michael@0 1913 }
michael@0 1914 mAddressTypesMap = new HashMap<String, Integer>();
michael@0 1915
michael@0 1916 mAddressTypesMap.put("home", StructuredPostal.TYPE_HOME);
michael@0 1917 mAddressTypesMap.put("work", StructuredPostal.TYPE_WORK);
michael@0 1918 }
michael@0 1919
michael@0 1920 private int getPhoneType(String phoneType) {
michael@0 1921 initPhoneTypesMap();
michael@0 1922 Integer type = mPhoneTypesMap.get(phoneType.toLowerCase());
michael@0 1923 return (type != null ? Integer.valueOf(type) : Phone.TYPE_CUSTOM);
michael@0 1924 }
michael@0 1925
michael@0 1926 private void initPhoneTypesMap() {
michael@0 1927 if (mPhoneTypesMap != null) {
michael@0 1928 return;
michael@0 1929 }
michael@0 1930 mPhoneTypesMap = new HashMap<String, Integer>();
michael@0 1931
michael@0 1932 mPhoneTypesMap.put("home", Phone.TYPE_HOME);
michael@0 1933 mPhoneTypesMap.put("mobile", Phone.TYPE_MOBILE);
michael@0 1934 mPhoneTypesMap.put("work", Phone.TYPE_WORK);
michael@0 1935 mPhoneTypesMap.put("fax home", Phone.TYPE_FAX_HOME);
michael@0 1936 mPhoneTypesMap.put("fax work", Phone.TYPE_FAX_WORK);
michael@0 1937 mPhoneTypesMap.put("pager", Phone.TYPE_PAGER);
michael@0 1938 mPhoneTypesMap.put("callback", Phone.TYPE_CALLBACK);
michael@0 1939 mPhoneTypesMap.put("car", Phone.TYPE_CAR);
michael@0 1940 mPhoneTypesMap.put("company main", Phone.TYPE_COMPANY_MAIN);
michael@0 1941 mPhoneTypesMap.put("isdn", Phone.TYPE_ISDN);
michael@0 1942 mPhoneTypesMap.put("main", Phone.TYPE_MAIN);
michael@0 1943 mPhoneTypesMap.put("fax other", Phone.TYPE_OTHER_FAX);
michael@0 1944 mPhoneTypesMap.put("other fax", Phone.TYPE_OTHER_FAX);
michael@0 1945 mPhoneTypesMap.put("radio", Phone.TYPE_RADIO);
michael@0 1946 mPhoneTypesMap.put("telex", Phone.TYPE_TELEX);
michael@0 1947 mPhoneTypesMap.put("tty", Phone.TYPE_TTY_TDD);
michael@0 1948 mPhoneTypesMap.put("ttd", Phone.TYPE_TTY_TDD);
michael@0 1949 mPhoneTypesMap.put("work mobile", Phone.TYPE_WORK_MOBILE);
michael@0 1950 mPhoneTypesMap.put("work pager", Phone.TYPE_WORK_PAGER);
michael@0 1951 mPhoneTypesMap.put("assistant", Phone.TYPE_ASSISTANT);
michael@0 1952 mPhoneTypesMap.put("mms", Phone.TYPE_MMS);
michael@0 1953 }
michael@0 1954
michael@0 1955 private int getEmailType(String emailType) {
michael@0 1956 initEmailTypesMap();
michael@0 1957 Integer type = mEmailTypesMap.get(emailType.toLowerCase());
michael@0 1958 return (type != null ? Integer.valueOf(type) : Email.TYPE_CUSTOM);
michael@0 1959 }
michael@0 1960
michael@0 1961 private void initEmailTypesMap() {
michael@0 1962 if (mEmailTypesMap != null) {
michael@0 1963 return;
michael@0 1964 }
michael@0 1965 mEmailTypesMap = new HashMap<String, Integer>();
michael@0 1966
michael@0 1967 mEmailTypesMap.put("home", Email.TYPE_HOME);
michael@0 1968 mEmailTypesMap.put("mobile", Email.TYPE_MOBILE);
michael@0 1969 mEmailTypesMap.put("work", Email.TYPE_WORK);
michael@0 1970 }
michael@0 1971
michael@0 1972 private int getWebsiteType(String webisteType) {
michael@0 1973 initWebsiteTypesMap();
michael@0 1974 Integer type = mWebsiteTypesMap.get(webisteType.toLowerCase());
michael@0 1975 return (type != null ? Integer.valueOf(type) : Website.TYPE_CUSTOM);
michael@0 1976 }
michael@0 1977
michael@0 1978 private void initWebsiteTypesMap() {
michael@0 1979 if (mWebsiteTypesMap != null) {
michael@0 1980 return;
michael@0 1981 }
michael@0 1982 mWebsiteTypesMap = new HashMap<String, Integer>();
michael@0 1983
michael@0 1984 mWebsiteTypesMap.put("homepage", Website.TYPE_HOMEPAGE);
michael@0 1985 mWebsiteTypesMap.put("blog", Website.TYPE_BLOG);
michael@0 1986 mWebsiteTypesMap.put("profile", Website.TYPE_PROFILE);
michael@0 1987 mWebsiteTypesMap.put("home", Website.TYPE_HOME);
michael@0 1988 mWebsiteTypesMap.put("work", Website.TYPE_WORK);
michael@0 1989 mWebsiteTypesMap.put("ftp", Website.TYPE_FTP);
michael@0 1990 }
michael@0 1991
michael@0 1992 private int getImType(String imType) {
michael@0 1993 initImTypesMap();
michael@0 1994 Integer type = mImTypesMap.get(imType.toLowerCase());
michael@0 1995 return (type != null ? Integer.valueOf(type) : Im.TYPE_CUSTOM);
michael@0 1996 }
michael@0 1997
michael@0 1998 private void initImTypesMap() {
michael@0 1999 if (mImTypesMap != null) {
michael@0 2000 return;
michael@0 2001 }
michael@0 2002 mImTypesMap = new HashMap<String, Integer>();
michael@0 2003
michael@0 2004 mImTypesMap.put("home", Im.TYPE_HOME);
michael@0 2005 mImTypesMap.put("work", Im.TYPE_WORK);
michael@0 2006 }
michael@0 2007
michael@0 2008 private String[] getAllColumns() {
michael@0 2009 return new String[] {Entity.DATA_ID, Data.MIMETYPE, Data.IS_SUPER_PRIMARY,
michael@0 2010 Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4,
michael@0 2011 Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8,
michael@0 2012 Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12,
michael@0 2013 Data.DATA13, Data.DATA14, Data.DATA15};
michael@0 2014 }
michael@0 2015 }

mercurial