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