michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.Collections; michael@0: import java.util.Comparator; michael@0: import java.util.HashMap; michael@0: import java.util.List; michael@0: import java.util.Map.Entry; michael@0: michael@0: import org.json.JSONArray; michael@0: import org.json.JSONException; michael@0: import org.json.JSONObject; michael@0: import org.mozilla.gecko.util.GeckoEventListener; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: michael@0: import android.accounts.Account; michael@0: import android.accounts.AccountManager; michael@0: import android.app.AlertDialog; michael@0: import android.content.ContentProviderOperation; michael@0: import android.content.ContentProviderResult; michael@0: import android.content.ContentResolver; michael@0: import android.content.ContentUris; michael@0: import android.content.ContentValues; michael@0: import android.content.DialogInterface; michael@0: import android.content.OperationApplicationException; michael@0: import android.database.Cursor; michael@0: import android.net.Uri; michael@0: import android.os.Build; michael@0: import android.os.RemoteException; michael@0: import android.provider.ContactsContract; michael@0: import android.provider.ContactsContract.CommonDataKinds.BaseTypes; michael@0: import android.provider.ContactsContract.CommonDataKinds.Email; michael@0: import android.provider.ContactsContract.CommonDataKinds.Event; michael@0: import android.provider.ContactsContract.CommonDataKinds.GroupMembership; michael@0: import android.provider.ContactsContract.CommonDataKinds.Im; michael@0: import android.provider.ContactsContract.CommonDataKinds.Nickname; michael@0: import android.provider.ContactsContract.CommonDataKinds.Note; michael@0: import android.provider.ContactsContract.CommonDataKinds.Organization; michael@0: import android.provider.ContactsContract.CommonDataKinds.Phone; michael@0: import android.provider.ContactsContract.CommonDataKinds.StructuredName; michael@0: import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; michael@0: import android.provider.ContactsContract.CommonDataKinds.Website; michael@0: import android.provider.ContactsContract.Data; michael@0: import android.provider.ContactsContract.Groups; michael@0: import android.provider.ContactsContract.RawContacts; michael@0: import android.provider.ContactsContract.RawContacts.Entity; michael@0: import android.telephony.PhoneNumberUtils; michael@0: import android.util.Log; michael@0: michael@0: public class ContactService implements GeckoEventListener { michael@0: private static final String LOGTAG = "GeckoContactService"; michael@0: private static final boolean DEBUG = false; michael@0: michael@0: private final static int GROUP_ACCOUNT_NAME = 0; michael@0: private final static int GROUP_ACCOUNT_TYPE = 1; michael@0: private final static int GROUP_ID = 2; michael@0: private final static int GROUP_TITLE = 3; michael@0: private final static int GROUP_AUTO_ADD = 4; michael@0: michael@0: private final static String CARRIER_COLUMN = Data.DATA5; michael@0: private final static String CUSTOM_DATA_COLUMN = Data.DATA1; michael@0: michael@0: // Pre-Honeycomb versions of Android have a "My Contacts" system group that all contacts are michael@0: // assigned to by default for a given account. After Honeycomb, an AUTO_ADD database column michael@0: // was added to denote groups that contacts are automatically added to michael@0: private final static String PRE_HONEYCOMB_DEFAULT_GROUP = "System Group: My Contacts"; michael@0: private final static String MIMETYPE_ADDITIONAL_NAME = "org.mozilla.gecko/additional_name"; michael@0: private final static String MIMETYPE_SEX = "org.mozilla.gecko/sex"; michael@0: private final static String MIMETYPE_GENDER_IDENTITY = "org.mozilla.gecko/gender_identity"; michael@0: private final static String MIMETYPE_KEY = "org.mozilla.gecko/key"; michael@0: private final static String MIMETYPE_MOZILLA_CONTACTS_FLAG = "org.mozilla.gecko/contact_flag"; michael@0: michael@0: private final EventDispatcher mEventDispatcher; michael@0: michael@0: private String mAccountName; michael@0: private String mAccountType; michael@0: private String mGroupTitle; michael@0: private long mGroupId; michael@0: private boolean mGotDeviceAccount; michael@0: michael@0: private HashMap mColumnNameConstantsMap; michael@0: private HashMap mMimeTypeConstantsMap; michael@0: private HashMap mAddressTypesMap; michael@0: private HashMap mPhoneTypesMap; michael@0: private HashMap mEmailTypesMap; michael@0: private HashMap mWebsiteTypesMap; michael@0: private HashMap mImTypesMap; michael@0: michael@0: private ContentResolver mContentResolver; michael@0: private GeckoApp mActivity; michael@0: michael@0: ContactService(EventDispatcher eventDispatcher, GeckoApp activity) { michael@0: mEventDispatcher = eventDispatcher; michael@0: mActivity = activity; michael@0: mContentResolver = mActivity.getContentResolver(); michael@0: mGotDeviceAccount = false; michael@0: michael@0: registerEventListener("Android:Contacts:Clear"); michael@0: registerEventListener("Android:Contacts:Find"); michael@0: registerEventListener("Android:Contacts:GetAll"); michael@0: registerEventListener("Android:Contacts:GetCount"); michael@0: registerEventListener("Android:Contact:Remove"); michael@0: registerEventListener("Android:Contact:Save"); michael@0: } michael@0: michael@0: public void destroy() { michael@0: unregisterEventListener("Android:Contacts:Clear"); michael@0: unregisterEventListener("Android:Contacts:Find"); michael@0: unregisterEventListener("Android:Contacts:GetAll"); michael@0: unregisterEventListener("Android:Contacts:GetCount"); michael@0: unregisterEventListener("Android:Contact:Remove"); michael@0: unregisterEventListener("Android:Contact:Save"); michael@0: } michael@0: michael@0: @Override michael@0: public void handleMessage(final String event, final JSONObject message) { michael@0: // If the account chooser dialog needs shown to the user, the message handling becomes michael@0: // asychronous so it needs posted to a background thread from the UI thread when the michael@0: // account chooser dialog is dismissed by the user. michael@0: Runnable handleMessage = new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: if (DEBUG) { michael@0: Log.d(LOGTAG, "Event: " + event + "\nMessage: " + message.toString(3)); michael@0: } michael@0: michael@0: final JSONObject messageData = message.getJSONObject("data"); michael@0: final String requestID = messageData.getString("requestID"); michael@0: michael@0: // Options may not exist for all operations michael@0: JSONObject contactOptions = messageData.optJSONObject("options"); michael@0: michael@0: if ("Android:Contacts:Find".equals(event)) { michael@0: findContacts(contactOptions, requestID); michael@0: } else if ("Android:Contacts:GetAll".equals(event)) { michael@0: getAllContacts(messageData, requestID); michael@0: } else if ("Android:Contacts:Clear".equals(event)) { michael@0: clearAllContacts(contactOptions, requestID); michael@0: } else if ("Android:Contact:Save".equals(event)) { michael@0: saveContact(contactOptions, requestID); michael@0: } else if ("Android:Contact:Remove".equals(event)) { michael@0: removeContact(contactOptions, requestID); michael@0: } else if ("Android:Contacts:GetCount".equals(event)) { michael@0: getContactsCount(requestID); michael@0: } else { michael@0: throw new IllegalArgumentException("Unexpected event: " + event); michael@0: } michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException("Message: " + e); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: // Get the account name/type if they haven't been set yet michael@0: if (!mGotDeviceAccount) { michael@0: getDeviceAccount(handleMessage); michael@0: } else { michael@0: handleMessage.run(); michael@0: } michael@0: } michael@0: michael@0: private void findContacts(final JSONObject contactOptions, final String requestID) { michael@0: long[] rawContactIds = findContactsRawIds(contactOptions); michael@0: Log.i(LOGTAG, "Got " + (rawContactIds != null ? rawContactIds.length : "null") + " raw contact IDs"); michael@0: michael@0: final String[] sortOptions = getSortOptionsFromJSON(contactOptions); michael@0: michael@0: if (rawContactIds == null || sortOptions == null) { michael@0: sendCallbackToJavascript("Android:Contacts:Find:Return:KO", requestID, null, null); michael@0: } else { michael@0: sendCallbackToJavascript("Android:Contacts:Find:Return:OK", requestID, michael@0: new String[] {"contacts"}, michael@0: new Object[] {getContactsAsJSONArray(rawContactIds, sortOptions[0], michael@0: sortOptions[1])}); michael@0: } michael@0: } michael@0: michael@0: private void getAllContacts(final JSONObject contactOptions, final String requestID) { michael@0: long[] rawContactIds = getAllRawContactIds(); michael@0: Log.i(LOGTAG, "Got " + rawContactIds.length + " raw contact IDs"); michael@0: michael@0: final String[] sortOptions = getSortOptionsFromJSON(contactOptions); michael@0: michael@0: if (rawContactIds == null || sortOptions == null) { michael@0: // There's no failure message for getAll michael@0: return; michael@0: } else { michael@0: sendCallbackToJavascript("Android:Contacts:GetAll:Next", requestID, michael@0: new String[] {"contacts"}, michael@0: new Object[] {getContactsAsJSONArray(rawContactIds, sortOptions[0], michael@0: sortOptions[1])}); michael@0: } michael@0: } michael@0: michael@0: private static String[] getSortOptionsFromJSON(final JSONObject contactOptions) { michael@0: String sortBy = null; michael@0: String sortOrder = null; michael@0: michael@0: try { michael@0: final JSONObject findOptions = contactOptions.getJSONObject("findOptions"); michael@0: sortBy = findOptions.optString("sortBy").toLowerCase(); michael@0: sortOrder = findOptions.optString("sortOrder").toLowerCase(); michael@0: michael@0: if ("".equals(sortBy)) { michael@0: sortBy = null; michael@0: } michael@0: if ("".equals(sortOrder)) { michael@0: sortOrder = "ascending"; michael@0: } michael@0: michael@0: // Only "familyname" and "givenname" are valid sortBy values and only "ascending" michael@0: // and "descending" are valid sortOrder values michael@0: if ((sortBy != null && !"familyname".equals(sortBy) && !"givenname".equals(sortBy)) || michael@0: (!"ascending".equals(sortOrder) && !"descending".equals(sortOrder))) { michael@0: return null; michael@0: } michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: michael@0: return new String[] {sortBy, sortOrder}; michael@0: } michael@0: michael@0: private long[] findContactsRawIds(final JSONObject contactOptions) { michael@0: List rawContactIds = new ArrayList(); michael@0: Cursor cursor = null; michael@0: michael@0: try { michael@0: final JSONObject findOptions = contactOptions.getJSONObject("findOptions"); michael@0: String filterValue = findOptions.optString("filterValue"); michael@0: JSONArray filterBy = findOptions.optJSONArray("filterBy"); michael@0: final String filterOp = findOptions.optString("filterOp"); michael@0: final int filterLimit = findOptions.getInt("filterLimit"); michael@0: final int substringMatching = findOptions.getInt("substringMatching"); michael@0: michael@0: // If filter value is undefined, avoid all the logic below and just return michael@0: // all available raw contact IDs michael@0: if ("".equals(filterValue) || "".equals(filterOp)) { michael@0: long[] allRawContactIds = getAllRawContactIds(); michael@0: michael@0: // Truncate the raw contacts IDs array if necessary michael@0: if (filterLimit > 0 && allRawContactIds.length > filterLimit) { michael@0: long[] truncatedRawContactIds = new long[filterLimit]; michael@0: for (int i = 0; i < filterLimit; i++) { michael@0: truncatedRawContactIds[i] = allRawContactIds[i]; michael@0: } michael@0: return truncatedRawContactIds; michael@0: } michael@0: return allRawContactIds; michael@0: } michael@0: michael@0: // "match" can only be used with the "tel" field michael@0: if ("match".equals(filterOp)) { michael@0: for (int i = 0; i < filterBy.length(); i++) { michael@0: if (!"tel".equals(filterBy.getString(i))) { michael@0: Log.w(LOGTAG, "\"match\" filterBy option is only valid for the \"tel\" field"); michael@0: return null; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Only select contacts from the selected account michael@0: String selection = null; michael@0: String[] selectionArgs = null; michael@0: michael@0: if (mAccountName != null) { michael@0: selection = RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?"; michael@0: selectionArgs = new String[] {mAccountName, mAccountType}; michael@0: } michael@0: michael@0: michael@0: final String[] columnsToGet; michael@0: michael@0: // If a filterBy value was not specified, search all columns michael@0: if (filterBy == null || filterBy.length() == 0) { michael@0: columnsToGet = null; michael@0: } else { michael@0: // Only get the columns given in the filterBy array michael@0: List columnsToGetList = new ArrayList(); michael@0: michael@0: columnsToGetList.add(Data.RAW_CONTACT_ID); michael@0: columnsToGetList.add(Data.MIMETYPE); michael@0: for (int i = 0; i < filterBy.length(); i++) { michael@0: final String field = filterBy.getString(i); michael@0: michael@0: // If one of the filterBy fields is the ID, just return the filter value michael@0: // which should be the ID michael@0: if ("id".equals(field)) { michael@0: try { michael@0: return new long[] {Long.valueOf(filterValue)}; michael@0: } catch (NumberFormatException e) { michael@0: // If the ID couldn't be converted to a long, it's invalid data michael@0: // so return null for failure michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: final String columnName = getColumnNameConstant(field); michael@0: michael@0: if (columnName != null) { michael@0: columnsToGetList.add(columnName); michael@0: } else { michael@0: Log.w(LOGTAG, "Unknown filter option: " + field); michael@0: } michael@0: } michael@0: michael@0: columnsToGet = columnsToGetList.toArray(new String[columnsToGetList.size()]); michael@0: } michael@0: michael@0: // Execute the query michael@0: cursor = mContentResolver.query(Data.CONTENT_URI, columnsToGet, selection, michael@0: selectionArgs, null); michael@0: michael@0: if (cursor.getCount() > 0) { michael@0: cursor.moveToPosition(-1); michael@0: while (cursor.moveToNext()) { michael@0: String mimeType = cursor.getString(cursor.getColumnIndex(Data.MIMETYPE)); michael@0: michael@0: // Check if the current mimetype is one of the types to filter by michael@0: if (filterBy != null && filterBy.length() > 0) { michael@0: for (int i = 0; i < filterBy.length(); i++) { michael@0: String currentFilterBy = filterBy.getString(i); michael@0: michael@0: if (mimeType.equals(getMimeTypeOfField(currentFilterBy))) { michael@0: String columnName = getColumnNameConstant(currentFilterBy); michael@0: int columnIndex = cursor.getColumnIndex(columnName); michael@0: String databaseValue = cursor.getString(columnIndex); michael@0: michael@0: boolean isPhone = false; michael@0: if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: isPhone = true; michael@0: } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: // Translate the group ID to the group name for matching michael@0: try { michael@0: databaseValue = getGroupName(Long.valueOf(databaseValue)); michael@0: } catch (NumberFormatException e) { michael@0: Log.e(LOGTAG, "Number Format Exception", e); michael@0: continue; michael@0: } michael@0: } else if (databaseValue == null) { michael@0: continue; michael@0: } michael@0: michael@0: // Check if the value matches the filter value michael@0: if (isFindMatch(filterOp, filterValue, databaseValue, isPhone, substringMatching)) { michael@0: addMatchToList(cursor, rawContactIds); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: // If no filterBy options were given, check each column for a match michael@0: int numColumns = cursor.getColumnCount(); michael@0: for (int i = 0; i < numColumns; i++) { michael@0: String databaseValue = cursor.getString(i); michael@0: if (databaseValue != null && isFindMatch(filterOp, filterValue, databaseValue, false, substringMatching)) { michael@0: addMatchToList(cursor, rawContactIds); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If the max found contacts size has been hit, stop looking for contacts michael@0: // A filter limit of 0 denotes there is no limit michael@0: if (filterLimit > 0 && filterLimit <= rawContactIds.size()) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } finally { michael@0: if (cursor != null) { michael@0: cursor.close(); michael@0: } michael@0: } michael@0: michael@0: // Return the contact IDs list converted to an array michael@0: return convertLongListToArray(rawContactIds); michael@0: } michael@0: michael@0: private boolean isFindMatch(final String filterOp, String filterValue, String databaseValue, michael@0: final boolean isPhone, final int substringMatching) { michael@0: Log.i(LOGTAG, "matching: filterOp: " + filterOp); michael@0: if (DEBUG) { michael@0: Log.d(LOGTAG, "matching: filterValue: " + filterValue); michael@0: Log.d(LOGTAG, "matching: databaseValue: " + databaseValue); michael@0: } michael@0: Log.i(LOGTAG, "matching: isPhone: " + isPhone); michael@0: Log.i(LOGTAG, "matching: substringMatching: " + substringMatching); michael@0: michael@0: if (databaseValue == null) { michael@0: return false; michael@0: } michael@0: michael@0: filterValue = filterValue.toLowerCase(); michael@0: databaseValue = databaseValue.toLowerCase(); michael@0: michael@0: if ("match".equals(filterOp)) { michael@0: // If substring matching is a positive number, only pay attention to the last X characters michael@0: // of both the filter and database values michael@0: if (substringMatching > 0) { michael@0: databaseValue = substringStartFromEnd(cleanPhoneNumber(databaseValue), substringMatching); michael@0: filterValue = substringStartFromEnd(cleanPhoneNumber(filterValue), substringMatching); michael@0: return databaseValue.startsWith(filterValue); michael@0: } michael@0: return databaseValue.equals(filterValue); michael@0: } else if ("equals".equals(filterOp)) { michael@0: if (isPhone) { michael@0: return PhoneNumberUtils.compare(filterValue, databaseValue); michael@0: } michael@0: return databaseValue.equals(filterValue); michael@0: } else if ("contains".equals(filterOp)) { michael@0: if (isPhone) { michael@0: filterValue = cleanPhoneNumber(filterValue); michael@0: databaseValue = cleanPhoneNumber(databaseValue); michael@0: } michael@0: return databaseValue.contains(filterValue); michael@0: } else if ("startsWith".equals(filterOp)) { michael@0: // If a phone number, remove non-dialable characters and then only pay attention to michael@0: // the last X digits given by the substring matching values (see bug 877302) michael@0: if (isPhone) { michael@0: String cleanedDatabasePhone = cleanPhoneNumber(databaseValue); michael@0: if (substringMatching > 0) { michael@0: cleanedDatabasePhone = substringStartFromEnd(cleanedDatabasePhone, substringMatching); michael@0: } michael@0: michael@0: if (cleanedDatabasePhone.startsWith(filterValue)) { michael@0: return true; michael@0: } michael@0: } michael@0: return databaseValue.startsWith(filterValue); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: private static String cleanPhoneNumber(String phone) { michael@0: return phone.replace(" ", "").replace("(", "").replace(")", "").replace("-", ""); michael@0: } michael@0: michael@0: private static String substringStartFromEnd(final String string, final int distanceFromEnd) { michael@0: int stringLen = string.length(); michael@0: if (stringLen < distanceFromEnd) { michael@0: return string; michael@0: } michael@0: return string.substring(stringLen - distanceFromEnd); michael@0: } michael@0: michael@0: private static void addMatchToList(final Cursor cursor, List rawContactIds) { michael@0: long rawContactId = cursor.getLong(cursor.getColumnIndex(Data.RAW_CONTACT_ID)); michael@0: if (!rawContactIds.contains(rawContactId)) { michael@0: rawContactIds.add(rawContactId); michael@0: } michael@0: } michael@0: michael@0: private JSONArray getContactsAsJSONArray(final long[] rawContactIds, final String sortBy, final String sortOrder) { michael@0: List contactsList = new ArrayList(); michael@0: JSONArray contactsArray = new JSONArray(); michael@0: michael@0: // Get each contact as a JSON object michael@0: for (int i = 0; i < rawContactIds.length; i++) { michael@0: contactsList.add(getContactAsJSONObject(rawContactIds[i])); michael@0: } michael@0: michael@0: // Sort the contacts michael@0: if (sortBy != null) { michael@0: Collections.sort(contactsList, new ContactsComparator(sortBy, sortOrder)); michael@0: } michael@0: michael@0: // Convert the contacts list to a JSON array michael@0: for (int i = 0; i < contactsList.size(); i++) { michael@0: contactsArray.put(contactsList.get(i)); michael@0: } michael@0: michael@0: return contactsArray; michael@0: } michael@0: michael@0: private JSONObject getContactAsJSONObject(long rawContactId) { michael@0: // ContactManager wants a contact object with it's properties wrapped in an array of objects michael@0: JSONObject contact = new JSONObject(); michael@0: JSONObject contactProperties = new JSONObject(); michael@0: michael@0: JSONArray names = new JSONArray(); michael@0: JSONArray givenNames = new JSONArray(); michael@0: JSONArray familyNames = new JSONArray(); michael@0: JSONArray honorificPrefixes = new JSONArray(); michael@0: JSONArray honorificSuffixes = new JSONArray(); michael@0: JSONArray additionalNames = new JSONArray(); michael@0: JSONArray nicknames = new JSONArray(); michael@0: JSONArray addresses = new JSONArray(); michael@0: JSONArray phones = new JSONArray(); michael@0: JSONArray emails = new JSONArray(); michael@0: JSONArray organizations = new JSONArray(); michael@0: JSONArray jobTitles = new JSONArray(); michael@0: JSONArray notes = new JSONArray(); michael@0: JSONArray urls = new JSONArray(); michael@0: JSONArray impps = new JSONArray(); michael@0: JSONArray categories = new JSONArray(); michael@0: String bday = null; michael@0: String anniversary = null; michael@0: String sex = null; michael@0: String genderIdentity = null; michael@0: JSONArray key = new JSONArray(); michael@0: michael@0: // Get all the data columns michael@0: final String[] columnsToGet = getAllColumns(); michael@0: michael@0: Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); michael@0: Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY); michael@0: michael@0: Cursor cursor = mContentResolver.query(entityUri, columnsToGet, null, null, null); michael@0: cursor.moveToPosition(-1); michael@0: while (cursor.moveToNext()) { michael@0: String mimeType = cursor.getString(cursor.getColumnIndex(Data.MIMETYPE)); michael@0: michael@0: // Put the proper fields for each mimetype into the JSON arrays michael@0: try { michael@0: if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: final String displayName = cursor.getString(cursor.getColumnIndex(StructuredName.DISPLAY_NAME)); michael@0: final String givenName = cursor.getString(cursor.getColumnIndex(StructuredName.GIVEN_NAME)); michael@0: final String familyName = cursor.getString(cursor.getColumnIndex(StructuredName.FAMILY_NAME)); michael@0: final String prefix = cursor.getString(cursor.getColumnIndex(StructuredName.PREFIX)); michael@0: final String suffix = cursor.getString(cursor.getColumnIndex(StructuredName.SUFFIX)); michael@0: michael@0: if (displayName != null) { michael@0: names.put(displayName); michael@0: } michael@0: if (givenName != null) { michael@0: givenNames.put(givenName); michael@0: } michael@0: if (familyName != null) { michael@0: familyNames.put(familyName); michael@0: } michael@0: if (prefix != null) { michael@0: honorificPrefixes.put(prefix); michael@0: } michael@0: if (suffix != null) { michael@0: honorificSuffixes.put(suffix); michael@0: } michael@0: michael@0: } else if (MIMETYPE_ADDITIONAL_NAME.equals(mimeType)) { michael@0: additionalNames.put(cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN))); michael@0: michael@0: } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: nicknames.put(cursor.getString(cursor.getColumnIndex(Nickname.NAME))); michael@0: michael@0: } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: initAddressTypesMap(); michael@0: getAddressDataAsJSONObject(cursor, addresses); michael@0: michael@0: } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: initPhoneTypesMap(); michael@0: getPhoneDataAsJSONObject(cursor, phones); michael@0: michael@0: } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: initEmailTypesMap(); michael@0: getGenericDataAsJSONObject(cursor, emails, Email.ADDRESS, Email.TYPE, Email.LABEL, mEmailTypesMap); michael@0: michael@0: } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: getOrganizationDataAsJSONObject(cursor, organizations, jobTitles); michael@0: michael@0: } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: notes.put(cursor.getString(cursor.getColumnIndex(Note.NOTE))); michael@0: michael@0: } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: initWebsiteTypesMap(); michael@0: getGenericDataAsJSONObject(cursor, urls, Website.URL, Website.TYPE, Website.LABEL, mWebsiteTypesMap); michael@0: michael@0: } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: initImTypesMap(); michael@0: getGenericDataAsJSONObject(cursor, impps, Im.DATA, Im.TYPE, Im.LABEL, mImTypesMap); michael@0: michael@0: } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: long groupId = cursor.getLong(cursor.getColumnIndex(GroupMembership.GROUP_ROW_ID)); michael@0: String groupName = getGroupName(groupId); michael@0: if (!doesJSONArrayContainString(categories, groupName)) { michael@0: categories.put(groupName); michael@0: } michael@0: michael@0: } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) { michael@0: int type = cursor.getInt(cursor.getColumnIndex(Event.TYPE)); michael@0: String date = cursor.getString(cursor.getColumnIndex(Event.START_DATE)); michael@0: michael@0: // Add the time info onto the date so it correctly parses into a JS date object michael@0: date += "T00:00:00"; michael@0: michael@0: switch (type) { michael@0: case Event.TYPE_BIRTHDAY: michael@0: bday = date; michael@0: break; michael@0: michael@0: case Event.TYPE_ANNIVERSARY: michael@0: anniversary = date; michael@0: break; michael@0: } michael@0: michael@0: } else if (MIMETYPE_SEX.equals(mimeType)) { michael@0: sex = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)); michael@0: michael@0: } else if (MIMETYPE_GENDER_IDENTITY.equals(mimeType)) { michael@0: genderIdentity = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)); michael@0: michael@0: } else if (MIMETYPE_KEY.equals(mimeType)) { michael@0: key.put(cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN))); michael@0: } michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: } michael@0: cursor.close(); michael@0: michael@0: try { michael@0: // Add the fields to the contact properties object michael@0: contactProperties.put("name", names); michael@0: contactProperties.put("givenName", givenNames); michael@0: contactProperties.put("familyName", familyNames); michael@0: contactProperties.put("honorificPrefix", honorificPrefixes); michael@0: contactProperties.put("honorificSuffix", honorificSuffixes); michael@0: contactProperties.put("additionalName", additionalNames); michael@0: contactProperties.put("nickname", nicknames); michael@0: contactProperties.put("adr", addresses); michael@0: contactProperties.put("tel", phones); michael@0: contactProperties.put("email", emails); michael@0: contactProperties.put("org", organizations); michael@0: contactProperties.put("jobTitle", jobTitles); michael@0: contactProperties.put("note", notes); michael@0: contactProperties.put("url", urls); michael@0: contactProperties.put("impp", impps); michael@0: contactProperties.put("category", categories); michael@0: contactProperties.put("key", key); michael@0: michael@0: putPossibleNullValueInJSONObject("bday", bday, contactProperties); michael@0: putPossibleNullValueInJSONObject("anniversary", anniversary, contactProperties); michael@0: putPossibleNullValueInJSONObject("sex", sex, contactProperties); michael@0: putPossibleNullValueInJSONObject("genderIdentity", genderIdentity, contactProperties); michael@0: michael@0: // Add the raw contact ID and the properties to the contact michael@0: contact.put("id", String.valueOf(rawContactId)); michael@0: contact.put("updated", null); michael@0: contact.put("published", null); michael@0: contact.put("properties", contactProperties); michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: try { michael@0: Log.d(LOGTAG, "Got contact: " + contact.toString(3)); michael@0: } catch (JSONException e) {} michael@0: } michael@0: michael@0: return contact; michael@0: } michael@0: michael@0: private boolean bool(int integer) { michael@0: return integer != 0 ? true : false; michael@0: } michael@0: michael@0: private void getGenericDataAsJSONObject(Cursor cursor, JSONArray array, final String dataColumn, michael@0: final String typeColumn, final String typeLabelColumn, michael@0: final HashMap typeMap) throws JSONException { michael@0: String value = cursor.getString(cursor.getColumnIndex(dataColumn)); michael@0: int typeConstant = cursor.getInt(cursor.getColumnIndex(typeColumn)); michael@0: String type; michael@0: if (typeConstant == BaseTypes.TYPE_CUSTOM) { michael@0: type = cursor.getString(cursor.getColumnIndex(typeLabelColumn)); michael@0: } else { michael@0: type = getKeyFromMapValue(typeMap, Integer.valueOf(typeConstant)); michael@0: } michael@0: michael@0: // Since an object may have multiple types, it may have already been added, michael@0: // but still needs the new type added michael@0: boolean found = false; michael@0: if (type != null) { michael@0: for (int i = 0; i < array.length(); i++) { michael@0: JSONObject object = array.getJSONObject(i); michael@0: if (value.equals(object.getString("value"))) { michael@0: found = true; michael@0: michael@0: JSONArray types = object.getJSONArray("type"); michael@0: if (!doesJSONArrayContainString(types, type)) { michael@0: types.put(type); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If an existing object wasn't found, make a new one michael@0: if (!found) { michael@0: JSONObject object = new JSONObject(); michael@0: JSONArray types = new JSONArray(); michael@0: object.put("value", value); michael@0: types.put(type); michael@0: object.put("type", types); michael@0: object.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Data.IS_SUPER_PRIMARY)))); michael@0: michael@0: array.put(object); michael@0: } michael@0: } michael@0: michael@0: private void getPhoneDataAsJSONObject(Cursor cursor, JSONArray phones) throws JSONException { michael@0: String value = cursor.getString(cursor.getColumnIndex(Phone.NUMBER)); michael@0: int typeConstant = cursor.getInt(cursor.getColumnIndex(Phone.TYPE)); michael@0: String type; michael@0: if (typeConstant == Phone.TYPE_CUSTOM) { michael@0: type = cursor.getString(cursor.getColumnIndex(Phone.LABEL)); michael@0: } else { michael@0: type = getKeyFromMapValue(mPhoneTypesMap, Integer.valueOf(typeConstant)); michael@0: } michael@0: michael@0: // Since a phone may have multiple types, it may have already been added, michael@0: // but still needs the new type added michael@0: boolean found = false; michael@0: if (type != null) { michael@0: for (int i = 0; i < phones.length(); i++) { michael@0: JSONObject phone = phones.getJSONObject(i); michael@0: if (value.equals(phone.getString("value"))) { michael@0: found = true; michael@0: michael@0: JSONArray types = phone.getJSONArray("type"); michael@0: if (!doesJSONArrayContainString(types, type)) { michael@0: types.put(type); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If an existing phone wasn't found, make a new one michael@0: if (!found) { michael@0: JSONObject phone = new JSONObject(); michael@0: JSONArray types = new JSONArray(); michael@0: phone.put("value", value); michael@0: phone.put("type", type); michael@0: types.put(type); michael@0: phone.put("type", types); michael@0: phone.put("carrier", cursor.getString(cursor.getColumnIndex(CARRIER_COLUMN))); michael@0: phone.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Phone.IS_SUPER_PRIMARY)))); michael@0: michael@0: phones.put(phone); michael@0: } michael@0: } michael@0: michael@0: private void getAddressDataAsJSONObject(Cursor cursor, JSONArray addresses) throws JSONException { michael@0: String streetAddress = cursor.getString(cursor.getColumnIndex(StructuredPostal.STREET)); michael@0: String locality = cursor.getString(cursor.getColumnIndex(StructuredPostal.CITY)); michael@0: String region = cursor.getString(cursor.getColumnIndex(StructuredPostal.REGION)); michael@0: String postalCode = cursor.getString(cursor.getColumnIndex(StructuredPostal.POSTCODE)); michael@0: String countryName = cursor.getString(cursor.getColumnIndex(StructuredPostal.COUNTRY)); michael@0: int typeConstant = cursor.getInt(cursor.getColumnIndex(StructuredPostal.TYPE)); michael@0: String type; michael@0: if (typeConstant == StructuredPostal.TYPE_CUSTOM) { michael@0: type = cursor.getString(cursor.getColumnIndex(StructuredPostal.LABEL)); michael@0: } else { michael@0: type = getKeyFromMapValue(mAddressTypesMap, Integer.valueOf(typeConstant)); michael@0: } michael@0: michael@0: // Since an email may have multiple types, it may have already been added, michael@0: // but still needs the new type added michael@0: boolean found = false; michael@0: if (type != null) { michael@0: for (int i = 0; i < addresses.length(); i++) { michael@0: JSONObject address = addresses.getJSONObject(i); michael@0: if (streetAddress.equals(address.getString("streetAddress")) && michael@0: locality.equals(address.getString("locality")) && michael@0: region.equals(address.getString("region")) && michael@0: countryName.equals(address.getString("countryName")) && michael@0: postalCode.equals(address.getString("postalCode"))) { michael@0: found = true; michael@0: michael@0: JSONArray types = address.getJSONArray("type"); michael@0: if (!doesJSONArrayContainString(types, type)) { michael@0: types.put(type); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If an existing email wasn't found, make a new one michael@0: if (!found) { michael@0: JSONObject address = new JSONObject(); michael@0: JSONArray types = new JSONArray(); michael@0: address.put("streetAddress", streetAddress); michael@0: address.put("locality", locality); michael@0: address.put("region", region); michael@0: address.put("countryName", countryName); michael@0: address.put("postalCode", postalCode); michael@0: types.put(type); michael@0: address.put("type", types); michael@0: address.put("pref", bool(cursor.getInt(cursor.getColumnIndex(StructuredPostal.IS_SUPER_PRIMARY)))); michael@0: michael@0: addresses.put(address); michael@0: } michael@0: } michael@0: michael@0: private void getOrganizationDataAsJSONObject(Cursor cursor, JSONArray organizations, michael@0: JSONArray jobTitles) throws JSONException { michael@0: int organizationColumnIndex = cursor.getColumnIndex(Organization.COMPANY); michael@0: int titleColumnIndex = cursor.getColumnIndex(Organization.TITLE); michael@0: michael@0: if (!cursor.isNull(organizationColumnIndex)) { michael@0: organizations.put(cursor.getString(organizationColumnIndex)); michael@0: } michael@0: if (!cursor.isNull(titleColumnIndex)) { michael@0: jobTitles.put(cursor.getString(titleColumnIndex)); michael@0: } michael@0: } michael@0: michael@0: private class ContactsComparator implements Comparator { michael@0: final String mSortBy; michael@0: final String mSortOrder; michael@0: michael@0: public ContactsComparator(final String sortBy, final String sortOrder) { michael@0: mSortBy = sortBy.toLowerCase(); michael@0: mSortOrder = sortOrder.toLowerCase(); michael@0: } michael@0: michael@0: @Override michael@0: public int compare(JSONObject left, JSONObject right) { michael@0: // Determine if sorting by "family name, given name" or "given name, family name" michael@0: boolean familyFirst = false; michael@0: if ("familyname".equals(mSortBy)) { michael@0: familyFirst = true; michael@0: } michael@0: michael@0: JSONObject leftProperties; michael@0: JSONObject rightProperties; michael@0: try { michael@0: leftProperties = left.getJSONObject("properties"); michael@0: rightProperties = right.getJSONObject("properties"); michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: michael@0: JSONArray leftFamilyNames = leftProperties.optJSONArray("familyName"); michael@0: JSONArray leftGivenNames = leftProperties.optJSONArray("givenName"); michael@0: JSONArray rightFamilyNames = rightProperties.optJSONArray("familyName"); michael@0: JSONArray rightGivenNames = rightProperties.optJSONArray("givenName"); michael@0: michael@0: // If any of the name arrays didn't exist (are null), create empty arrays michael@0: // to avoid doing a bunch of null checking below michael@0: if (leftFamilyNames == null) { michael@0: leftFamilyNames = new JSONArray(); michael@0: } michael@0: if (leftGivenNames == null) { michael@0: leftGivenNames = new JSONArray(); michael@0: } michael@0: if (rightFamilyNames == null) { michael@0: rightFamilyNames = new JSONArray(); michael@0: } michael@0: if (rightGivenNames == null) { michael@0: rightGivenNames = new JSONArray(); michael@0: } michael@0: michael@0: int maxArrayLength = max(leftFamilyNames.length(), leftGivenNames.length(), michael@0: rightFamilyNames.length(), rightGivenNames.length()); michael@0: michael@0: int index = 0; michael@0: int compareResult; michael@0: do { michael@0: // Join together the given name and family name per the pattern above michael@0: String leftName = ""; michael@0: String rightName = ""; michael@0: michael@0: if (familyFirst) { michael@0: leftName = leftFamilyNames.optString(index, "") + leftGivenNames.optString(index, ""); michael@0: rightName = rightFamilyNames.optString(index, "") + rightGivenNames.optString(index, ""); michael@0: } else { michael@0: leftName = leftGivenNames.optString(index, "") + leftFamilyNames.optString(index, ""); michael@0: rightName = rightGivenNames.optString(index, "") + rightFamilyNames.optString(index, ""); michael@0: } michael@0: michael@0: index++; michael@0: compareResult = leftName.compareTo(rightName); michael@0: michael@0: } while (compareResult == 0 && index < maxArrayLength); michael@0: michael@0: // If descending order, flip the result michael@0: if (compareResult != 0 && "descending".equals(mSortOrder)) { michael@0: compareResult = -compareResult; michael@0: } michael@0: michael@0: return compareResult; michael@0: } michael@0: } michael@0: michael@0: private void clearAllContacts(final JSONObject contactOptions, final String requestID) { michael@0: ArrayList deleteOptions = new ArrayList(); michael@0: michael@0: // Delete all contacts from the selected account michael@0: ContentProviderOperation.Builder deleteOptionsBuilder = ContentProviderOperation.newDelete(RawContacts.CONTENT_URI); michael@0: if (mAccountName != null) { michael@0: deleteOptionsBuilder.withSelection(RawContacts.ACCOUNT_NAME + "=?", new String[] {mAccountName}) michael@0: .withSelection(RawContacts.ACCOUNT_TYPE + "=?", new String[] {mAccountType}); michael@0: } michael@0: michael@0: deleteOptions.add(deleteOptionsBuilder.build()); michael@0: michael@0: // Clear the contacts michael@0: String returnStatus = "KO"; michael@0: if (applyBatch(deleteOptions) != null) { michael@0: returnStatus = "OK"; michael@0: } michael@0: michael@0: Log.i(LOGTAG, "Sending return status: " + returnStatus); michael@0: michael@0: sendCallbackToJavascript("Android:Contacts:Clear:Return:" + returnStatus, requestID, michael@0: new String[] {"contactID"}, new Object[] {"undefined"}); michael@0: michael@0: } michael@0: michael@0: private boolean deleteContact(String rawContactId) { michael@0: ContentProviderOperation deleteOptions = ContentProviderOperation.newDelete(RawContacts.CONTENT_URI) michael@0: .withSelection(RawContacts._ID + "=?", michael@0: new String[] {rawContactId}) michael@0: .build(); michael@0: michael@0: ArrayList deleteOptionsList = new ArrayList(); michael@0: deleteOptionsList.add(deleteOptions); michael@0: michael@0: return checkForPositiveCountInResults(applyBatch(deleteOptionsList)); michael@0: } michael@0: michael@0: private void removeContact(final JSONObject contactOptions, final String requestID) { michael@0: String rawContactId; michael@0: try { michael@0: rawContactId = contactOptions.getString("id"); michael@0: Log.i(LOGTAG, "Removing contact with ID: " + rawContactId); michael@0: } catch (JSONException e) { michael@0: // We can't continue without a raw contact ID michael@0: sendCallbackToJavascript("Android:Contact:Remove:Return:KO", requestID, null, null); michael@0: return; michael@0: } michael@0: michael@0: String returnStatus = "KO"; michael@0: if(deleteContact(rawContactId)) { michael@0: returnStatus = "OK"; michael@0: } michael@0: michael@0: sendCallbackToJavascript("Android:Contact:Remove:Return:" + returnStatus, requestID, michael@0: new String[] {"contactID"}, new Object[] {rawContactId}); michael@0: } michael@0: michael@0: private void saveContact(final JSONObject contactOptions, final String requestID) { michael@0: try { michael@0: String reason = contactOptions.getString("reason"); michael@0: JSONObject contact = contactOptions.getJSONObject("contact"); michael@0: JSONObject contactProperties = contact.getJSONObject("properties"); michael@0: michael@0: if ("update".equals(reason)) { michael@0: updateContact(contactProperties, contact.getLong("id"), requestID); michael@0: } else { michael@0: insertContact(contactProperties, requestID); michael@0: } michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: } michael@0: michael@0: private void insertContact(final JSONObject contactProperties, final String requestID) throws JSONException { michael@0: ArrayList newContactOptions = new ArrayList(); michael@0: michael@0: // Account to save the contact under michael@0: newContactOptions.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) michael@0: .withValue(RawContacts.ACCOUNT_NAME, mAccountName) michael@0: .withValue(RawContacts.ACCOUNT_TYPE, mAccountType) michael@0: .build()); michael@0: michael@0: List newContactValues = getContactValues(contactProperties); michael@0: michael@0: for (ContentValues values : newContactValues) { michael@0: newContactOptions.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) michael@0: .withValueBackReference(Data.RAW_CONTACT_ID, 0) michael@0: .withValues(values) michael@0: .build()); michael@0: } michael@0: michael@0: String returnStatus = "KO"; michael@0: Long newRawContactId = new Long(-1); michael@0: michael@0: // Insert the contact! michael@0: ContentProviderResult[] insertResults = applyBatch(newContactOptions); michael@0: michael@0: if (insertResults != null) { michael@0: try { michael@0: // Get the ID of the newly created contact michael@0: newRawContactId = getRawContactIdFromContentProviderResults(insertResults); michael@0: michael@0: if (newRawContactId != null) { michael@0: returnStatus = "OK"; michael@0: } michael@0: } catch (NumberFormatException e) { michael@0: Log.e(LOGTAG, "NumberFormatException", e); michael@0: } michael@0: michael@0: Log.i(LOGTAG, "Newly created contact ID: " + newRawContactId); michael@0: } michael@0: michael@0: Log.i(LOGTAG, "Sending return status: " + returnStatus); michael@0: michael@0: sendCallbackToJavascript("Android:Contact:Save:Return:" + returnStatus, requestID, michael@0: new String[] {"contactID", "reason"}, michael@0: new Object[] {newRawContactId, "create"}); michael@0: } michael@0: michael@0: private void updateContact(final JSONObject contactProperties, final long rawContactId, final String requestID) throws JSONException { michael@0: // Why is updating a contact so weird and horribly inefficient? Because Android doesn't michael@0: // like multiple values for contact fields, but the Mozilla contacts API calls for this. michael@0: // This means the Android update function is essentially completely useless. Why not just michael@0: // delete the contact and re-insert it? Because that would change the contact ID and the michael@0: // Mozilla contacts API shouldn't have this behavior. The solution is to delete each michael@0: // row from the contacts data table that belongs to the contact, and insert the new michael@0: // fields. But then why not just delete all the data from the data in one go and michael@0: // insert the new data in another? Because if all the data relating to a contact is michael@0: // deleted, Android will "conviently" remove the ID making it impossible to insert data michael@0: // under the old ID. To work around this, we put a Mozilla contact flag in the database michael@0: michael@0: ContentProviderOperation removeOptions = ContentProviderOperation.newDelete(Data.CONTENT_URI) michael@0: .withSelection(Data.RAW_CONTACT_ID + "=? AND " + michael@0: Data.MIMETYPE + " != '" + MIMETYPE_MOZILLA_CONTACTS_FLAG + "'", michael@0: new String[] {String.valueOf(rawContactId)}) michael@0: .build(); michael@0: michael@0: ArrayList removeOptionsList = new ArrayList(); michael@0: removeOptionsList.add(removeOptions); michael@0: michael@0: ContentProviderResult[] removeResults = applyBatch(removeOptionsList); michael@0: michael@0: // Check if the remove failed michael@0: if (removeResults == null || !checkForPositiveCountInResults(removeResults)) { michael@0: Log.w(LOGTAG, "Null or 0 remove results"); michael@0: michael@0: sendCallbackToJavascript("Android:Contact:Save:Return:KO", requestID, null, null); michael@0: return; michael@0: } michael@0: michael@0: List updateContactValues = getContactValues(contactProperties); michael@0: ArrayList updateContactOptions = new ArrayList(); michael@0: michael@0: for (ContentValues values : updateContactValues) { michael@0: updateContactOptions.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) michael@0: .withValue(Data.RAW_CONTACT_ID, rawContactId) michael@0: .withValues(values) michael@0: .build()); michael@0: } michael@0: michael@0: String returnStatus = "KO"; michael@0: michael@0: // Update the contact! michael@0: applyBatch(updateContactOptions); michael@0: michael@0: sendCallbackToJavascript("Android:Contact:Save:Return:OK", requestID, michael@0: new String[] {"contactID", "reason"}, michael@0: new Object[] {rawContactId, "update"}); michael@0: } michael@0: michael@0: private List getContactValues(final JSONObject contactProperties) throws JSONException { michael@0: List contactValues = new ArrayList(); michael@0: michael@0: // Add the contact to the default group so it is shown in other apps michael@0: // like the Contacts or People app michael@0: ContentValues defaultGroupValues = new ContentValues(); michael@0: defaultGroupValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); michael@0: defaultGroupValues.put(GroupMembership.GROUP_ROW_ID, mGroupId); michael@0: contactValues.add(defaultGroupValues); michael@0: michael@0: // Create all the values that will be inserted into the new contact michael@0: getNameValues(contactProperties.optJSONArray("name"), michael@0: contactProperties.optJSONArray("givenName"), michael@0: contactProperties.optJSONArray("familyName"), michael@0: contactProperties.optJSONArray("honorificPrefix"), michael@0: contactProperties.optJSONArray("honorificSuffix"), michael@0: contactValues); michael@0: michael@0: getGenericValues(MIMETYPE_ADDITIONAL_NAME, CUSTOM_DATA_COLUMN, michael@0: contactProperties.optJSONArray("additionalName"), contactValues); michael@0: michael@0: getNicknamesValues(contactProperties.optJSONArray("nickname"), contactValues); michael@0: michael@0: getAddressesValues(contactProperties.optJSONArray("adr"), contactValues); michael@0: michael@0: getPhonesValues(contactProperties.optJSONArray("tel"), contactValues); michael@0: michael@0: getEmailsValues(contactProperties.optJSONArray("email"), contactValues); michael@0: michael@0: //getPhotosValues(contactProperties.optJSONArray("photo"), contactValues); michael@0: michael@0: getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.COMPANY, michael@0: contactProperties.optJSONArray("org"), contactValues); michael@0: michael@0: getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.TITLE, michael@0: contactProperties.optJSONArray("jobTitle"), contactValues); michael@0: michael@0: getNotesValues(contactProperties.optJSONArray("note"), contactValues); michael@0: michael@0: getWebsitesValues(contactProperties.optJSONArray("url"), contactValues); michael@0: michael@0: getImsValues(contactProperties.optJSONArray("impp"), contactValues); michael@0: michael@0: getCategoriesValues(contactProperties.optJSONArray("category"), contactValues); michael@0: michael@0: getEventValues(contactProperties.optString("bday"), Event.TYPE_BIRTHDAY, contactValues); michael@0: michael@0: getEventValues(contactProperties.optString("anniversary"), Event.TYPE_ANNIVERSARY, contactValues); michael@0: michael@0: getCustomMimetypeValues(contactProperties.optString("sex"), MIMETYPE_SEX, contactValues); michael@0: michael@0: getCustomMimetypeValues(contactProperties.optString("genderIdentity"), MIMETYPE_GENDER_IDENTITY, contactValues); michael@0: michael@0: getGenericValues(MIMETYPE_KEY, CUSTOM_DATA_COLUMN, contactProperties.optJSONArray("key"), michael@0: contactValues); michael@0: michael@0: return contactValues; michael@0: } michael@0: michael@0: private void getGenericValues(final String mimeType, final String dataType, final JSONArray fields, michael@0: List newContactValues) throws JSONException { michael@0: if (fields == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < fields.length(); i++) { michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, mimeType); michael@0: contentValues.put(dataType, fields.getString(i)); michael@0: newContactValues.add(contentValues); michael@0: } michael@0: } michael@0: michael@0: private void getNameValues(final JSONArray displayNames, final JSONArray givenNames, michael@0: final JSONArray familyNames, final JSONArray prefixes, michael@0: final JSONArray suffixes, List newContactValues) throws JSONException { michael@0: int maxLen = max((displayNames != null ? displayNames.length() : 0), michael@0: (givenNames != null ? givenNames.length() : 0), michael@0: (familyNames != null ? familyNames.length() : 0), michael@0: (prefixes != null ? prefixes.length() : 0), michael@0: (suffixes != null ? suffixes.length() : 0)); michael@0: michael@0: for (int i = 0; i < maxLen; i++) { michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); michael@0: michael@0: final String displayName = (displayNames != null ? displayNames.optString(i, null) : null); michael@0: final String givenName = (givenNames != null ? givenNames.optString(i, null) : null); michael@0: final String familyName = (familyNames != null ? familyNames.optString(i, null) : null); michael@0: final String prefix = (prefixes != null ? prefixes.optString(i, null) : null); michael@0: final String suffix = (suffixes != null ? suffixes.optString(i, null) : null); michael@0: michael@0: if (displayName != null) { michael@0: contentValues.put(StructuredName.DISPLAY_NAME, displayName); michael@0: } michael@0: if (givenName != null) { michael@0: contentValues.put(StructuredName.GIVEN_NAME, givenName); michael@0: } michael@0: if (familyName != null) { michael@0: contentValues.put(StructuredName.FAMILY_NAME, familyName); michael@0: } michael@0: if (prefix != null) { michael@0: contentValues.put(StructuredName.PREFIX, prefix); michael@0: } michael@0: if (suffix != null) { michael@0: contentValues.put(StructuredName.SUFFIX, suffix); michael@0: } michael@0: michael@0: newContactValues.add(contentValues); michael@0: } michael@0: } michael@0: michael@0: private void getNicknamesValues(final JSONArray nicknames, List newContactValues) throws JSONException { michael@0: if (nicknames == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < nicknames.length(); i++) { michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); michael@0: contentValues.put(Nickname.NAME, nicknames.getString(i)); michael@0: contentValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT); michael@0: newContactValues.add(contentValues); michael@0: } michael@0: } michael@0: michael@0: private void getAddressesValues(final JSONArray addresses, List newContactValues) throws JSONException { michael@0: if (addresses == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < addresses.length(); i++) { michael@0: JSONObject address = addresses.getJSONObject(i); michael@0: JSONArray addressTypes = address.optJSONArray("type"); michael@0: michael@0: if (addressTypes != null) { michael@0: for (int j = 0; j < addressTypes.length(); j++) { michael@0: // Translate the address type string to an integer constant michael@0: // provided by the ContactsContract API michael@0: final String type = addressTypes.getString(j); michael@0: final int typeConstant = getAddressType(type); michael@0: michael@0: newContactValues.add(createAddressContentValues(address, typeConstant, type)); michael@0: } michael@0: } else { michael@0: newContactValues.add(createAddressContentValues(address, -1, null)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private ContentValues createAddressContentValues(final JSONObject address, final int typeConstant, michael@0: final String type) throws JSONException { michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); michael@0: contentValues.put(StructuredPostal.STREET, address.optString("streetAddress")); michael@0: contentValues.put(StructuredPostal.CITY, address.optString("locality")); michael@0: contentValues.put(StructuredPostal.REGION, address.optString("region")); michael@0: contentValues.put(StructuredPostal.POSTCODE, address.optString("postalCode")); michael@0: contentValues.put(StructuredPostal.COUNTRY, address.optString("countryName")); michael@0: michael@0: if (type != null) { michael@0: contentValues.put(StructuredPostal.TYPE, typeConstant); michael@0: michael@0: // If a custom type, add a label michael@0: if (typeConstant == BaseTypes.TYPE_CUSTOM) { michael@0: contentValues.put(StructuredPostal.LABEL, type); michael@0: } michael@0: } michael@0: michael@0: if (address.has("pref")) { michael@0: contentValues.put(Data.IS_SUPER_PRIMARY, address.getBoolean("pref") ? 1 : 0); michael@0: } michael@0: michael@0: return contentValues; michael@0: } michael@0: michael@0: private void getPhonesValues(final JSONArray phones, List newContactValues) throws JSONException { michael@0: if (phones == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < phones.length(); i++) { michael@0: JSONObject phone = phones.getJSONObject(i); michael@0: JSONArray phoneTypes = phone.optJSONArray("type"); michael@0: ContentValues contentValues; michael@0: michael@0: if (phoneTypes != null && phoneTypes.length() > 0) { michael@0: for (int j = 0; j < phoneTypes.length(); j++) { michael@0: // Translate the phone type string to an integer constant michael@0: // provided by the ContactsContract API michael@0: final String type = phoneTypes.getString(j); michael@0: final int typeConstant = getPhoneType(type); michael@0: michael@0: contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"), michael@0: typeConstant, type, phone.optBoolean("pref")); michael@0: if (phone.has("carrier")) { michael@0: contentValues.put(CARRIER_COLUMN, phone.optString("carrier")); michael@0: } michael@0: newContactValues.add(contentValues); michael@0: } michael@0: } else { michael@0: contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"), michael@0: -1, null, phone.optBoolean("pref")); michael@0: if (phone.has("carrier")) { michael@0: contentValues.put(CARRIER_COLUMN, phone.optString("carrier")); michael@0: } michael@0: newContactValues.add(contentValues); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private void getEmailsValues(final JSONArray emails, List newContactValues) throws JSONException { michael@0: if (emails == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < emails.length(); i++) { michael@0: JSONObject email = emails.getJSONObject(i); michael@0: JSONArray emailTypes = email.optJSONArray("type"); michael@0: michael@0: if (emailTypes != null && emailTypes.length() > 0) { michael@0: for (int j = 0; j < emailTypes.length(); j++) { michael@0: // Translate the email type string to an integer constant michael@0: // provided by the ContactsContract API michael@0: final String type = emailTypes.getString(j); michael@0: final int typeConstant = getEmailType(type); michael@0: michael@0: newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE, michael@0: email.optString("value"), michael@0: typeConstant, type, michael@0: email.optBoolean("pref"))); michael@0: } michael@0: } else { michael@0: newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE, michael@0: email.optString("value"), michael@0: -1, null, email.optBoolean("pref"))); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private void getPhotosValues(final JSONArray photos, List newContactValues) throws JSONException { michael@0: if (photos == null) { michael@0: return; michael@0: } michael@0: michael@0: // TODO: implement this michael@0: } michael@0: michael@0: private void getNotesValues(final JSONArray notes, List newContactValues) throws JSONException { michael@0: if (notes == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < notes.length(); i++) { michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE); michael@0: contentValues.put(Note.NOTE, notes.getString(i)); michael@0: newContactValues.add(contentValues); michael@0: } michael@0: } michael@0: michael@0: private void getWebsitesValues(final JSONArray websites, List newContactValues) throws JSONException { michael@0: if (websites == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < websites.length(); i++) { michael@0: JSONObject website = websites.getJSONObject(i); michael@0: JSONArray websiteTypes = website.optJSONArray("type"); michael@0: michael@0: if (websiteTypes != null && websiteTypes.length() > 0) { michael@0: for (int j = 0; j < websiteTypes.length(); j++) { michael@0: // Translate the website type string to an integer constant michael@0: // provided by the ContactsContract API michael@0: final String type = websiteTypes.getString(j); michael@0: final int typeConstant = getWebsiteType(type); michael@0: michael@0: newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE, michael@0: website.optString("value"), michael@0: typeConstant, type, michael@0: website.optBoolean("pref"))); michael@0: } michael@0: } else { michael@0: newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE, michael@0: website.optString("value"), michael@0: -1, null, website.optBoolean("pref"))); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private void getImsValues(final JSONArray ims, List newContactValues) throws JSONException { michael@0: if (ims == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < ims.length(); i++) { michael@0: JSONObject im = ims.getJSONObject(i); michael@0: JSONArray imTypes = im.optJSONArray("type"); michael@0: michael@0: if (imTypes != null && imTypes.length() > 0) { michael@0: for (int j = 0; j < imTypes.length(); j++) { michael@0: // Translate the IM type string to an integer constant michael@0: // provided by the ContactsContract API michael@0: final String type = imTypes.getString(j); michael@0: final int typeConstant = getImType(type); michael@0: michael@0: newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE, michael@0: im.optString("value"), michael@0: typeConstant, type, michael@0: im.optBoolean("pref"))); michael@0: } michael@0: } else { michael@0: newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE, michael@0: im.optString("value"), michael@0: -1, null, im.optBoolean("pref"))); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private void getCategoriesValues(final JSONArray categories, List newContactValues) throws JSONException { michael@0: if (categories == null) { michael@0: return; michael@0: } michael@0: michael@0: for (int i = 0; i < categories.length(); i++) { michael@0: String category = categories.getString(i); michael@0: michael@0: if ("my contacts".equals(category.toLowerCase()) || michael@0: PRE_HONEYCOMB_DEFAULT_GROUP.equalsIgnoreCase(category)) { michael@0: Log.w(LOGTAG, "New contacts are implicitly added to the default group."); michael@0: continue; michael@0: } michael@0: michael@0: // Find the group ID of the given category michael@0: long groupId = getGroupId(category); michael@0: michael@0: // Create the group if it doesn't already exist michael@0: if (groupId == -1) { michael@0: groupId = createGroup(category); michael@0: // If the group is still -1, we failed to create the group michael@0: if (groupId == -1) { michael@0: // Only log the category name if in debug michael@0: if (DEBUG) { michael@0: Log.d(LOGTAG, "Failed to create new group for category \"" + category + "\""); michael@0: } else { michael@0: Log.w(LOGTAG, "Failed to create new group for given category."); michael@0: } michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); michael@0: contentValues.put(GroupMembership.GROUP_ROW_ID, groupId); michael@0: newContactValues.add(contentValues); michael@0: michael@0: newContactValues.add(contentValues); michael@0: } michael@0: } michael@0: michael@0: private void getEventValues(final String event, final int type, List newContactValues) { michael@0: if (event == null || event.length() < 11) { michael@0: return; michael@0: } michael@0: michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); michael@0: contentValues.put(Event.START_DATE, event.substring(0, 10)); michael@0: contentValues.put(Event.TYPE, type); michael@0: newContactValues.add(contentValues); michael@0: } michael@0: michael@0: private void getCustomMimetypeValues(final String value, final String mimeType, List newContactValues) { michael@0: if (value == null || "null".equals(value)) { michael@0: return; michael@0: } michael@0: michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, mimeType); michael@0: contentValues.put(CUSTOM_DATA_COLUMN, value); michael@0: newContactValues.add(contentValues); michael@0: } michael@0: michael@0: private void getMozillaContactFlagValues(List newContactValues) { michael@0: try { michael@0: JSONArray mozillaContactsFlag = new JSONArray(); michael@0: mozillaContactsFlag.put("1"); michael@0: getGenericValues(MIMETYPE_MOZILLA_CONTACTS_FLAG, CUSTOM_DATA_COLUMN, mozillaContactsFlag, newContactValues); michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: } michael@0: michael@0: private ContentValues createContentValues(final String mimeType, final String value, final int typeConstant, michael@0: final String type, final boolean preferredValue) { michael@0: ContentValues contentValues = new ContentValues(); michael@0: contentValues.put(Data.MIMETYPE, mimeType); michael@0: contentValues.put(Data.DATA1, value); michael@0: contentValues.put(Data.IS_SUPER_PRIMARY, preferredValue ? 1 : 0); michael@0: michael@0: if (type != null) { michael@0: contentValues.put(Data.DATA2, typeConstant); michael@0: michael@0: // If a custom type, add a label michael@0: if (typeConstant == BaseTypes.TYPE_CUSTOM) { michael@0: contentValues.put(Data.DATA3, type); michael@0: } michael@0: } michael@0: michael@0: return contentValues; michael@0: } michael@0: michael@0: private void getContactsCount(final String requestID) { michael@0: Cursor cursor = getAllRawContactIdsCursor(); michael@0: Integer numContacts = Integer.valueOf(cursor.getCount()); michael@0: cursor.close(); michael@0: michael@0: sendCallbackToJavascript("Android:Contacts:Count", requestID, new String[] {"count"}, michael@0: new Object[] {numContacts}); michael@0: } michael@0: michael@0: private void sendCallbackToJavascript(final String subject, final String requestID, michael@0: final String[] argNames, final Object[] argValues) { michael@0: // Check the same number of argument names and arguments were given michael@0: if (argNames != null && argNames.length != argValues.length) { michael@0: throw new IllegalArgumentException("Argument names and argument values lengths do not match. " + michael@0: "Names length = " + argNames.length + ", Values length = " + michael@0: argValues.length); michael@0: } michael@0: michael@0: try { michael@0: JSONObject callbackMessage = new JSONObject(); michael@0: callbackMessage.put("requestID", requestID); michael@0: michael@0: if (argNames != null) { michael@0: for (int i = 0; i < argNames.length; i++) { michael@0: callbackMessage.put(argNames[i], argValues[i]); michael@0: } michael@0: } michael@0: michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(subject, callbackMessage.toString())); michael@0: } catch (JSONException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: } michael@0: michael@0: private void registerEventListener(final String event) { michael@0: mEventDispatcher.registerEventListener(event, this); michael@0: } michael@0: michael@0: private void unregisterEventListener(final String event) { michael@0: mEventDispatcher.unregisterEventListener(event, this); michael@0: } michael@0: michael@0: private ContentProviderResult[] applyBatch(ArrayList operations) { michael@0: try { michael@0: return mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations); michael@0: } catch (RemoteException e) { michael@0: Log.e(LOGTAG, "RemoteException", e); michael@0: } catch (OperationApplicationException e) { michael@0: Log.e(LOGTAG, "OperationApplicationException", e); michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: private void getDeviceAccount(final Runnable handleMessage) { michael@0: Account[] accounts = AccountManager.get(mActivity).getAccounts(); michael@0: michael@0: if (accounts.length == 0) { michael@0: Log.w(LOGTAG, "No accounts available"); michael@0: gotDeviceAccount(handleMessage); michael@0: } else if (accounts.length > 1) { michael@0: // Show the accounts chooser dialog if more than one dialog exists michael@0: showAccountsDialog(accounts, handleMessage); michael@0: } else { michael@0: // If only one account exists, use it michael@0: mAccountName = accounts[0].name; michael@0: mAccountType = accounts[0].type; michael@0: gotDeviceAccount(handleMessage); michael@0: } michael@0: michael@0: mGotDeviceAccount = true; michael@0: } michael@0: michael@0: private void showAccountsDialog(final Account[] accounts, final Runnable handleMessage) { michael@0: String[] accountNames = new String[accounts.length]; michael@0: for (int i = 0; i < accounts.length; i++) { michael@0: accountNames[i] = accounts[i].name; michael@0: } michael@0: michael@0: final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); michael@0: builder.setTitle(mActivity.getResources().getString(R.string.contacts_account_chooser_dialog_title)) michael@0: .setSingleChoiceItems(accountNames, 0, new DialogInterface.OnClickListener() { michael@0: @Override michael@0: public void onClick(DialogInterface dialog, int position) { michael@0: // Set the account name and type when an item is selected and dismiss the dialog michael@0: mAccountName = accounts[position].name; michael@0: mAccountType = accounts[position].type; michael@0: dialog.dismiss(); michael@0: gotDeviceAccount(handleMessage); michael@0: } michael@0: }); michael@0: michael@0: mActivity.runOnUiThread(new Runnable() { michael@0: public void run() { michael@0: builder.show(); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: private void gotDeviceAccount(final Runnable handleMessage) { michael@0: // Force the handleMessage runnable and getDefaultGroupId to run on the background thread michael@0: Runnable runnable = new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: getDefaultGroupId(); michael@0: michael@0: // Don't log a user's account if not debug mode. Otherwise, just log a message michael@0: // saying that we got an account to use michael@0: if (mAccountName == null) { michael@0: Log.i(LOGTAG, "No device account selected. Leaving account as null."); michael@0: } else if (DEBUG) { michael@0: Log.d(LOGTAG, "Using account: " + mAccountName + " (type: " + mAccountType + ")"); michael@0: } else { michael@0: Log.i(LOGTAG, "Got device account to use for contact operations."); michael@0: } michael@0: handleMessage.run(); michael@0: } michael@0: }; michael@0: michael@0: ThreadUtils.postToBackgroundThread(runnable); michael@0: } michael@0: michael@0: private void getDefaultGroupId() { michael@0: Cursor cursor = getAllGroups(); michael@0: michael@0: cursor.moveToPosition(-1); michael@0: while (cursor.moveToNext()) { michael@0: // Check if the account name and type for the group match the account name and type of michael@0: // the account we're working with michael@0: final String groupAccountName = cursor.getString(GROUP_ACCOUNT_NAME); michael@0: if (!groupAccountName.equals(mAccountName)) { michael@0: continue; michael@0: } michael@0: michael@0: final String groupAccountType = cursor.getString(GROUP_ACCOUNT_TYPE); michael@0: if (!groupAccountType.equals(mAccountType)) { michael@0: continue; michael@0: } michael@0: michael@0: // For all honeycomb and up, the default group is the first one which has the AUTO_ADD flag set michael@0: if (isAutoAddGroup(cursor)) { michael@0: mGroupTitle = cursor.getString(GROUP_TITLE); michael@0: mGroupId = cursor.getLong(GROUP_ID); michael@0: break; michael@0: } else if (PRE_HONEYCOMB_DEFAULT_GROUP.equals(cursor.getString(GROUP_TITLE))) { michael@0: mGroupId = cursor.getLong(GROUP_ID); michael@0: mGroupTitle = PRE_HONEYCOMB_DEFAULT_GROUP; michael@0: break; michael@0: } michael@0: } michael@0: cursor.close(); michael@0: michael@0: if (mGroupId == 0) { michael@0: Log.w(LOGTAG, "Default group ID not found. Newly created contacts will not belong to any groups."); michael@0: } else if (DEBUG) { michael@0: Log.i(LOGTAG, "Using group ID: " + mGroupId + " (" + mGroupTitle + ")"); michael@0: } michael@0: } michael@0: michael@0: private static boolean isAutoAddGroup(Cursor cursor) { michael@0: // For Honeycomb and up, the default group is the first one which has the AUTO_ADD flag set. michael@0: // For everything below Honeycomb, use the default "System Group: My Contacts" group michael@0: return (Build.VERSION.SDK_INT >= 11 && !cursor.isNull(GROUP_AUTO_ADD) && michael@0: cursor.getInt(GROUP_AUTO_ADD) != 0); michael@0: } michael@0: michael@0: private long getGroupId(String groupName) { michael@0: long groupId = -1; michael@0: Cursor cursor = getGroups(Groups.TITLE + " = '" + groupName + "'"); michael@0: michael@0: cursor.moveToPosition(-1); michael@0: while (cursor.moveToNext()) { michael@0: String groupAccountName = cursor.getString(GROUP_ACCOUNT_NAME); michael@0: String groupAccountType = cursor.getString(GROUP_ACCOUNT_TYPE); michael@0: michael@0: // Check if the account name and type for the group match the account name and type of michael@0: // the account we're working with or the default "Phone" account if no account was found michael@0: if (groupAccountName.equals(mAccountName) && groupAccountType.equals(mAccountType) || michael@0: (mAccountName == null && "Phone".equals(groupAccountType))) { michael@0: if (groupName.equals(cursor.getString(GROUP_TITLE))) { michael@0: groupId = cursor.getLong(GROUP_ID); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: cursor.close(); michael@0: michael@0: return groupId; michael@0: } michael@0: michael@0: private String getGroupName(long groupId) { michael@0: Cursor cursor = getGroups(Groups._ID + " = " + groupId); michael@0: michael@0: if (cursor.getCount() == 0) { michael@0: cursor.close(); michael@0: return null; michael@0: } michael@0: michael@0: cursor.moveToPosition(0); michael@0: String groupName = cursor.getString(cursor.getColumnIndex(Groups.TITLE)); michael@0: cursor.close(); michael@0: michael@0: return groupName; michael@0: } michael@0: michael@0: private Cursor getAllGroups() { michael@0: return getGroups(null); michael@0: } michael@0: michael@0: private Cursor getGroups(String selectArg) { michael@0: String[] columns = new String[] { michael@0: Groups.ACCOUNT_NAME, michael@0: Groups.ACCOUNT_TYPE, michael@0: Groups._ID, michael@0: Groups.TITLE, michael@0: (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Groups.AUTO_ADD : Groups._ID) michael@0: }; michael@0: michael@0: if (selectArg != null) { michael@0: selectArg = "AND " + selectArg; michael@0: } else { michael@0: selectArg = ""; michael@0: } michael@0: michael@0: return mContentResolver.query(Groups.CONTENT_URI, columns, michael@0: Groups.ACCOUNT_TYPE + " NOT NULL AND " + michael@0: Groups.ACCOUNT_NAME + " NOT NULL " + selectArg, null, null); michael@0: } michael@0: michael@0: private long createGroup(String groupName) { michael@0: if (DEBUG) { michael@0: Log.d(LOGTAG, "Creating group: " + groupName); michael@0: } michael@0: michael@0: ArrayList newGroupOptions = new ArrayList(); michael@0: michael@0: // Create the group under the account we're using michael@0: // If no account is selected, use a default account name/type for the group michael@0: newGroupOptions.add(ContentProviderOperation.newInsert(Groups.CONTENT_URI) michael@0: .withValue(Groups.ACCOUNT_NAME, (mAccountName == null ? "Phone" : mAccountName)) michael@0: .withValue(Groups.ACCOUNT_TYPE, (mAccountType == null ? "Phone" : mAccountType)) michael@0: .withValue(Groups.TITLE, groupName) michael@0: .withValue(Groups.GROUP_VISIBLE, true) michael@0: .build()); michael@0: michael@0: applyBatch(newGroupOptions); michael@0: michael@0: // Return the ID of the newly created group michael@0: return getGroupId(groupName); michael@0: } michael@0: michael@0: private long[] getAllRawContactIds() { michael@0: Cursor cursor = getAllRawContactIdsCursor(); michael@0: michael@0: // Put the ids into an array michael@0: long[] ids = new long[cursor.getCount()]; michael@0: int index = 0; michael@0: cursor.moveToPosition(-1); michael@0: while(cursor.moveToNext()) { michael@0: ids[index] = cursor.getLong(cursor.getColumnIndex(RawContacts._ID)); michael@0: index++; michael@0: } michael@0: cursor.close(); michael@0: michael@0: return ids; michael@0: } michael@0: michael@0: private Cursor getAllRawContactIdsCursor() { michael@0: // When a contact is deleted, it actually just sets the deleted field to 1 until the michael@0: // sync adapter actually deletes the contact later so ignore any contacts with the deleted michael@0: // flag set michael@0: String selection = RawContacts.DELETED + "=0"; michael@0: String[] selectionArgs = null; michael@0: michael@0: // Only get contacts from the selected account michael@0: if (mAccountName != null) { michael@0: selection += " AND " + RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?"; michael@0: selectionArgs = new String[] {mAccountName, mAccountType}; michael@0: } michael@0: michael@0: // Get the ID's of all contacts and use the number of contact ID's as michael@0: // the total number of contacts michael@0: return mContentResolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID}, michael@0: selection, selectionArgs, null); michael@0: } michael@0: michael@0: private static Long getRawContactIdFromContentProviderResults(ContentProviderResult[] results) throws NumberFormatException { michael@0: for (int i = 0; i < results.length; i++) { michael@0: if (results[i].uri == null) { michael@0: continue; michael@0: } michael@0: michael@0: String uri = results[i].uri.toString(); michael@0: // Check if the uri is from the raw contacts table michael@0: if (uri.contains("raw_contacts")) { michael@0: // The ID is the after the final forward slash in the URI michael@0: return Long.parseLong(uri.substring(uri.lastIndexOf("/") + 1)); michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: michael@0: private static boolean checkForPositiveCountInResults(ContentProviderResult[] results) { michael@0: for (int i = 0; i < results.length; i++) { michael@0: Integer count = results[i].count; michael@0: michael@0: if (DEBUG) { michael@0: Log.d(LOGTAG, "Results count: " + count); michael@0: } michael@0: michael@0: if (count != null && count > 0) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: private static long[] convertLongListToArray(List list) { michael@0: long[] array = new long[list.size()]; michael@0: michael@0: for (int i = 0; i < list.size(); i++) { michael@0: array[i] = list.get(i); michael@0: } michael@0: michael@0: return array; michael@0: } michael@0: michael@0: private static boolean doesJSONArrayContainString(final JSONArray array, final String value) { michael@0: for (int i = 0; i < array.length(); i++) { michael@0: if (value.equals(array.optString(i))) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: private static int max(int... values) { michael@0: int max = values[0]; michael@0: for (int value : values) { michael@0: if (value > max) { michael@0: max = value; michael@0: } michael@0: } michael@0: return max; michael@0: } michael@0: michael@0: private static void putPossibleNullValueInJSONObject(final String key, final Object value, JSONObject jsonObject) throws JSONException{ michael@0: if (value != null) { michael@0: jsonObject.put(key, value); michael@0: } else { michael@0: jsonObject.put(key, JSONObject.NULL); michael@0: } michael@0: } michael@0: michael@0: private static String getKeyFromMapValue(final HashMap map, Integer value) { michael@0: for (Entry entry : map.entrySet()) { michael@0: if (value == entry.getValue()) { michael@0: return entry.getKey(); michael@0: } michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: private String getColumnNameConstant(String field) { michael@0: initColumnNameConstantsMap(); michael@0: return mColumnNameConstantsMap.get(field.toLowerCase()); michael@0: } michael@0: michael@0: private void initColumnNameConstantsMap() { michael@0: if (mColumnNameConstantsMap != null) { michael@0: return; michael@0: } michael@0: mColumnNameConstantsMap = new HashMap(); michael@0: michael@0: mColumnNameConstantsMap.put("name", StructuredName.DISPLAY_NAME); michael@0: mColumnNameConstantsMap.put("givenname", StructuredName.GIVEN_NAME); michael@0: mColumnNameConstantsMap.put("familyname", StructuredName.FAMILY_NAME); michael@0: mColumnNameConstantsMap.put("honorificprefix", StructuredName.PREFIX); michael@0: mColumnNameConstantsMap.put("honorificsuffix", StructuredName.SUFFIX); michael@0: mColumnNameConstantsMap.put("additionalname", CUSTOM_DATA_COLUMN); michael@0: mColumnNameConstantsMap.put("nickname", Nickname.NAME); michael@0: mColumnNameConstantsMap.put("adr", StructuredPostal.STREET); michael@0: mColumnNameConstantsMap.put("email", Email.ADDRESS); michael@0: mColumnNameConstantsMap.put("url", Website.URL); michael@0: mColumnNameConstantsMap.put("category", GroupMembership.GROUP_ROW_ID); michael@0: mColumnNameConstantsMap.put("tel", Phone.NUMBER); michael@0: mColumnNameConstantsMap.put("org", Organization.COMPANY); michael@0: mColumnNameConstantsMap.put("jobTitle", Organization.TITLE); michael@0: mColumnNameConstantsMap.put("note", Note.NOTE); michael@0: mColumnNameConstantsMap.put("impp", Im.DATA); michael@0: mColumnNameConstantsMap.put("sex", CUSTOM_DATA_COLUMN); michael@0: mColumnNameConstantsMap.put("genderidentity", CUSTOM_DATA_COLUMN); michael@0: mColumnNameConstantsMap.put("key", CUSTOM_DATA_COLUMN); michael@0: } michael@0: michael@0: private String getMimeTypeOfField(String field) { michael@0: initMimeTypeConstantsMap(); michael@0: return mMimeTypeConstantsMap.get(field.toLowerCase()); michael@0: } michael@0: michael@0: private void initMimeTypeConstantsMap() { michael@0: if (mMimeTypeConstantsMap != null) { michael@0: return; michael@0: } michael@0: mMimeTypeConstantsMap = new HashMap(); michael@0: michael@0: mMimeTypeConstantsMap.put("name", StructuredName.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("givenname", StructuredName.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("familyname", StructuredName.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("honorificprefix", StructuredName.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("honorificsuffix", StructuredName.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("additionalname", MIMETYPE_ADDITIONAL_NAME); michael@0: mMimeTypeConstantsMap.put("nickname", Nickname.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("email", Email.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("url", Website.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("category", GroupMembership.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("tel", Phone.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("org", Organization.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("jobTitle", Organization.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("note", Note.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("impp", Im.CONTENT_ITEM_TYPE); michael@0: mMimeTypeConstantsMap.put("sex", MIMETYPE_SEX); michael@0: mMimeTypeConstantsMap.put("genderidentity", MIMETYPE_GENDER_IDENTITY); michael@0: mMimeTypeConstantsMap.put("key", MIMETYPE_KEY); michael@0: } michael@0: michael@0: private int getAddressType(String addressType) { michael@0: initAddressTypesMap(); michael@0: Integer type = mAddressTypesMap.get(addressType.toLowerCase()); michael@0: return (type != null ? Integer.valueOf(type) : StructuredPostal.TYPE_CUSTOM); michael@0: } michael@0: michael@0: private void initAddressTypesMap() { michael@0: if (mAddressTypesMap != null) { michael@0: return; michael@0: } michael@0: mAddressTypesMap = new HashMap(); michael@0: michael@0: mAddressTypesMap.put("home", StructuredPostal.TYPE_HOME); michael@0: mAddressTypesMap.put("work", StructuredPostal.TYPE_WORK); michael@0: } michael@0: michael@0: private int getPhoneType(String phoneType) { michael@0: initPhoneTypesMap(); michael@0: Integer type = mPhoneTypesMap.get(phoneType.toLowerCase()); michael@0: return (type != null ? Integer.valueOf(type) : Phone.TYPE_CUSTOM); michael@0: } michael@0: michael@0: private void initPhoneTypesMap() { michael@0: if (mPhoneTypesMap != null) { michael@0: return; michael@0: } michael@0: mPhoneTypesMap = new HashMap(); michael@0: michael@0: mPhoneTypesMap.put("home", Phone.TYPE_HOME); michael@0: mPhoneTypesMap.put("mobile", Phone.TYPE_MOBILE); michael@0: mPhoneTypesMap.put("work", Phone.TYPE_WORK); michael@0: mPhoneTypesMap.put("fax home", Phone.TYPE_FAX_HOME); michael@0: mPhoneTypesMap.put("fax work", Phone.TYPE_FAX_WORK); michael@0: mPhoneTypesMap.put("pager", Phone.TYPE_PAGER); michael@0: mPhoneTypesMap.put("callback", Phone.TYPE_CALLBACK); michael@0: mPhoneTypesMap.put("car", Phone.TYPE_CAR); michael@0: mPhoneTypesMap.put("company main", Phone.TYPE_COMPANY_MAIN); michael@0: mPhoneTypesMap.put("isdn", Phone.TYPE_ISDN); michael@0: mPhoneTypesMap.put("main", Phone.TYPE_MAIN); michael@0: mPhoneTypesMap.put("fax other", Phone.TYPE_OTHER_FAX); michael@0: mPhoneTypesMap.put("other fax", Phone.TYPE_OTHER_FAX); michael@0: mPhoneTypesMap.put("radio", Phone.TYPE_RADIO); michael@0: mPhoneTypesMap.put("telex", Phone.TYPE_TELEX); michael@0: mPhoneTypesMap.put("tty", Phone.TYPE_TTY_TDD); michael@0: mPhoneTypesMap.put("ttd", Phone.TYPE_TTY_TDD); michael@0: mPhoneTypesMap.put("work mobile", Phone.TYPE_WORK_MOBILE); michael@0: mPhoneTypesMap.put("work pager", Phone.TYPE_WORK_PAGER); michael@0: mPhoneTypesMap.put("assistant", Phone.TYPE_ASSISTANT); michael@0: mPhoneTypesMap.put("mms", Phone.TYPE_MMS); michael@0: } michael@0: michael@0: private int getEmailType(String emailType) { michael@0: initEmailTypesMap(); michael@0: Integer type = mEmailTypesMap.get(emailType.toLowerCase()); michael@0: return (type != null ? Integer.valueOf(type) : Email.TYPE_CUSTOM); michael@0: } michael@0: michael@0: private void initEmailTypesMap() { michael@0: if (mEmailTypesMap != null) { michael@0: return; michael@0: } michael@0: mEmailTypesMap = new HashMap(); michael@0: michael@0: mEmailTypesMap.put("home", Email.TYPE_HOME); michael@0: mEmailTypesMap.put("mobile", Email.TYPE_MOBILE); michael@0: mEmailTypesMap.put("work", Email.TYPE_WORK); michael@0: } michael@0: michael@0: private int getWebsiteType(String webisteType) { michael@0: initWebsiteTypesMap(); michael@0: Integer type = mWebsiteTypesMap.get(webisteType.toLowerCase()); michael@0: return (type != null ? Integer.valueOf(type) : Website.TYPE_CUSTOM); michael@0: } michael@0: michael@0: private void initWebsiteTypesMap() { michael@0: if (mWebsiteTypesMap != null) { michael@0: return; michael@0: } michael@0: mWebsiteTypesMap = new HashMap(); michael@0: michael@0: mWebsiteTypesMap.put("homepage", Website.TYPE_HOMEPAGE); michael@0: mWebsiteTypesMap.put("blog", Website.TYPE_BLOG); michael@0: mWebsiteTypesMap.put("profile", Website.TYPE_PROFILE); michael@0: mWebsiteTypesMap.put("home", Website.TYPE_HOME); michael@0: mWebsiteTypesMap.put("work", Website.TYPE_WORK); michael@0: mWebsiteTypesMap.put("ftp", Website.TYPE_FTP); michael@0: } michael@0: michael@0: private int getImType(String imType) { michael@0: initImTypesMap(); michael@0: Integer type = mImTypesMap.get(imType.toLowerCase()); michael@0: return (type != null ? Integer.valueOf(type) : Im.TYPE_CUSTOM); michael@0: } michael@0: michael@0: private void initImTypesMap() { michael@0: if (mImTypesMap != null) { michael@0: return; michael@0: } michael@0: mImTypesMap = new HashMap(); michael@0: michael@0: mImTypesMap.put("home", Im.TYPE_HOME); michael@0: mImTypesMap.put("work", Im.TYPE_WORK); michael@0: } michael@0: michael@0: private String[] getAllColumns() { michael@0: return new String[] {Entity.DATA_ID, Data.MIMETYPE, Data.IS_SUPER_PRIMARY, michael@0: Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, michael@0: Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8, michael@0: Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12, michael@0: Data.DATA13, Data.DATA14, Data.DATA15}; michael@0: } michael@0: }