diff --git a/js/ui/cordova/plugins/cordova-plugin-contacts/src/android/ContactAccessorSdk5.java b/js/ui/cordova/plugins/cordova-plugin-contacts/src/android/ContactAccessorSdk5.java new file mode 100644 index 0000000..bd43bf1 --- /dev/null +++ b/js/ui/cordova/plugins/cordova-plugin-contacts/src/android/ContactAccessorSdk5.java @@ -0,0 +1,2293 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova.contacts; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.sql.Date; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.cordova.CordovaInterface; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.text.TextUtils; +import android.util.Log; + +/** + * An implementation of {@link ContactAccessor} that uses current Contacts API. + * This class should be used on Eclair or beyond, but would not work on any earlier + * release of Android. As a matter of fact, it could not even be loaded. + *

+ * This implementation has several advantages: + *

+ */ + +public class ContactAccessorSdk5 extends ContactAccessor { + + /** + * Keep the photo size under the 1 MB blog limit. + */ + private static final long MAX_PHOTO_SIZE = 1048576; + + private static final String EMAIL_REGEXP = ".+@.+\\.+.+"; /* @.*/ + + private static final String ASSET_URL_PREFIX = "file:///android_asset/"; + + /** + * A static map that converts the JavaScript property name to Android database column name. + */ + private static final Map dbMap = new HashMap(); + + static { + dbMap.put("id", ContactsContract.Data.CONTACT_ID); + dbMap.put("displayName", ContactsContract.Contacts.DISPLAY_NAME); + dbMap.put("name", CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.formatted", CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.familyName", CommonDataKinds.StructuredName.FAMILY_NAME); + dbMap.put("name.givenName", CommonDataKinds.StructuredName.GIVEN_NAME); + dbMap.put("name.middleName", CommonDataKinds.StructuredName.MIDDLE_NAME); + dbMap.put("name.honorificPrefix", CommonDataKinds.StructuredName.PREFIX); + dbMap.put("name.honorificSuffix", CommonDataKinds.StructuredName.SUFFIX); + dbMap.put("nickname", CommonDataKinds.Nickname.NAME); + dbMap.put("phoneNumbers", CommonDataKinds.Phone.NUMBER); + dbMap.put("phoneNumbers.value", CommonDataKinds.Phone.NUMBER); + dbMap.put("emails", CommonDataKinds.Email.DATA); + dbMap.put("emails.value", CommonDataKinds.Email.DATA); + dbMap.put("addresses", CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.formatted", CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.streetAddress", CommonDataKinds.StructuredPostal.STREET); + dbMap.put("addresses.locality", CommonDataKinds.StructuredPostal.CITY); + dbMap.put("addresses.region", CommonDataKinds.StructuredPostal.REGION); + dbMap.put("addresses.postalCode", CommonDataKinds.StructuredPostal.POSTCODE); + dbMap.put("addresses.country", CommonDataKinds.StructuredPostal.COUNTRY); + dbMap.put("ims", CommonDataKinds.Im.DATA); + dbMap.put("ims.value", CommonDataKinds.Im.DATA); + dbMap.put("organizations", CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.name", CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.department", CommonDataKinds.Organization.DEPARTMENT); + dbMap.put("organizations.title", CommonDataKinds.Organization.TITLE); + dbMap.put("birthday", CommonDataKinds.Event.CONTENT_ITEM_TYPE); + dbMap.put("note", CommonDataKinds.Note.NOTE); + dbMap.put("photos.value", CommonDataKinds.Photo.CONTENT_ITEM_TYPE); + //dbMap.put("categories.value", null); + dbMap.put("urls", CommonDataKinds.Website.URL); + dbMap.put("urls.value", CommonDataKinds.Website.URL); + } + + /** + * Create an contact accessor. + */ + public ContactAccessorSdk5(CordovaInterface context) { + mApp = context; + } + + /** + * This method takes the fields required and search options in order to produce an + * array of contacts that matches the criteria provided. + * @param fields an array of items to be used as search criteria + * @param options that can be applied to contact searching + * @return an array of contacts + */ + @Override + public JSONArray search(JSONArray fields, JSONObject options) { + // Get the find options + String searchTerm = ""; + int limit = Integer.MAX_VALUE; + boolean multiple = true; + boolean hasPhoneNumber = false; + + if (options != null) { + searchTerm = options.optString("filter"); + if (searchTerm.length() == 0) { + searchTerm = "%"; + } + else { + searchTerm = "%" + searchTerm + "%"; + } + + try { + multiple = options.getBoolean("multiple"); + if (!multiple) { + limit = 1; + } + } catch (JSONException e) { + // Multiple was not specified so we assume the default is true. + Log.e(LOG_TAG, e.getMessage(), e); + } + + try { + hasPhoneNumber = options.getBoolean("hasPhoneNumber"); + } catch (JSONException e) { + // hasPhoneNumber was not specified so we assume the default is false. + } + } + else { + searchTerm = "%"; + } + + // Loop through the fields the user provided to see what data should be returned. + HashMap populate = buildPopulationSet(options); + + // Build the ugly where clause and where arguments for one big query. + WhereOptions whereOptions = buildWhereClause(fields, searchTerm, hasPhoneNumber); + + // Get all the id's where the search term matches the fields passed in. + Cursor idCursor = mApp.getActivity().getContentResolver().query(ContactsContract.Data.CONTENT_URI, + new String[] { ContactsContract.Data.CONTACT_ID }, + whereOptions.getWhere(), + whereOptions.getWhereArgs(), + ContactsContract.Data.CONTACT_ID + " ASC"); + + // Create a set of unique ids + Set contactIds = new HashSet(); + int idColumn = -1; + while (idCursor.moveToNext()) { + if (idColumn < 0) { + idColumn = idCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID); + } + contactIds.add(idCursor.getString(idColumn)); + } + idCursor.close(); + + // Build a query that only looks at ids + WhereOptions idOptions = buildIdClause(contactIds, searchTerm, hasPhoneNumber); + + // Determine which columns we should be fetching. + HashSet columnsToFetch = new HashSet(); + columnsToFetch.add(ContactsContract.Data.CONTACT_ID); + columnsToFetch.add(ContactsContract.Data.RAW_CONTACT_ID); + columnsToFetch.add(ContactsContract.Data.MIMETYPE); + + if (isRequired("displayName", populate)) { + columnsToFetch.add(CommonDataKinds.StructuredName.DISPLAY_NAME); + } + if (isRequired("name", populate)) { + columnsToFetch.add(CommonDataKinds.StructuredName.FAMILY_NAME); + columnsToFetch.add(CommonDataKinds.StructuredName.GIVEN_NAME); + columnsToFetch.add(CommonDataKinds.StructuredName.MIDDLE_NAME); + columnsToFetch.add(CommonDataKinds.StructuredName.PREFIX); + columnsToFetch.add(CommonDataKinds.StructuredName.SUFFIX); + } + if (isRequired("phoneNumbers", populate)) { + columnsToFetch.add(CommonDataKinds.Phone._ID); + columnsToFetch.add(CommonDataKinds.Phone.NUMBER); + columnsToFetch.add(CommonDataKinds.Phone.TYPE); + } + if (isRequired("emails", populate)) { + columnsToFetch.add(CommonDataKinds.Email._ID); + columnsToFetch.add(CommonDataKinds.Email.DATA); + columnsToFetch.add(CommonDataKinds.Email.TYPE); + } + if (isRequired("addresses", populate)) { + columnsToFetch.add(CommonDataKinds.StructuredPostal._ID); + columnsToFetch.add(CommonDataKinds.Organization.TYPE); + columnsToFetch.add(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + columnsToFetch.add(CommonDataKinds.StructuredPostal.STREET); + columnsToFetch.add(CommonDataKinds.StructuredPostal.CITY); + columnsToFetch.add(CommonDataKinds.StructuredPostal.REGION); + columnsToFetch.add(CommonDataKinds.StructuredPostal.POSTCODE); + columnsToFetch.add(CommonDataKinds.StructuredPostal.COUNTRY); + } + if (isRequired("organizations", populate)) { + columnsToFetch.add(CommonDataKinds.Organization._ID); + columnsToFetch.add(CommonDataKinds.Organization.TYPE); + columnsToFetch.add(CommonDataKinds.Organization.DEPARTMENT); + columnsToFetch.add(CommonDataKinds.Organization.COMPANY); + columnsToFetch.add(CommonDataKinds.Organization.TITLE); + } + if (isRequired("ims", populate)) { + columnsToFetch.add(CommonDataKinds.Im._ID); + columnsToFetch.add(CommonDataKinds.Im.DATA); + columnsToFetch.add(CommonDataKinds.Im.TYPE); + } + if (isRequired("note", populate)) { + columnsToFetch.add(CommonDataKinds.Note.NOTE); + } + if (isRequired("nickname", populate)) { + columnsToFetch.add(CommonDataKinds.Nickname.NAME); + } + if (isRequired("urls", populate)) { + columnsToFetch.add(CommonDataKinds.Website._ID); + columnsToFetch.add(CommonDataKinds.Website.URL); + columnsToFetch.add(CommonDataKinds.Website.TYPE); + } + if (isRequired("birthday", populate)) { + columnsToFetch.add(CommonDataKinds.Event.START_DATE); + columnsToFetch.add(CommonDataKinds.Event.TYPE); + } + if (isRequired("photos", populate)) { + columnsToFetch.add(CommonDataKinds.Photo._ID); + } + + // Do the id query + Cursor c = mApp.getActivity().getContentResolver().query(ContactsContract.Data.CONTENT_URI, + columnsToFetch.toArray(new String[] {}), + idOptions.getWhere(), + idOptions.getWhereArgs(), + ContactsContract.Data.CONTACT_ID + " ASC"); + + JSONArray contacts = populateContactArray(limit, populate, c); + + if (!c.isClosed()) { + c.close(); + } + return contacts; + } + + /** + * A special search that finds one contact by id + * + * @param id contact to find by id + * @return a JSONObject representing the contact + * @throws JSONException + */ + public JSONObject getContactById(String id) throws JSONException { + // Call overloaded version with no desiredFields + return getContactById(id, null); + } + + @Override + public JSONObject getContactById(String id, JSONArray desiredFields) throws JSONException { + // Do the id query + Cursor c = mApp.getActivity().getContentResolver().query( + ContactsContract.Data.CONTENT_URI, + null, + ContactsContract.Data.RAW_CONTACT_ID + " = ? ", + new String[] { id }, + ContactsContract.Data.RAW_CONTACT_ID + " ASC"); + + HashMap populate = buildPopulationSet( + new JSONObject().put("desiredFields", desiredFields) + ); + + JSONArray contacts = populateContactArray(1, populate, c); + + if (!c.isClosed()) { + c.close(); + } + + if (contacts.length() == 1) { + return contacts.getJSONObject(0); + } else { + return null; + } + } + + /** + * Creates an array of contacts from the cursor you pass in + * + * @param limit max number of contacts for the array + * @param populate whether or not you should populate a certain value + * @param c the cursor + * @return a JSONArray of contacts + */ + private JSONArray populateContactArray(int limit, + HashMap populate, Cursor c) { + + String contactId = ""; + String rawId = ""; + String oldContactId = ""; + boolean newContact = true; + String mimetype = ""; + + JSONArray contacts = new JSONArray(); + JSONObject contact = new JSONObject(); + JSONArray organizations = new JSONArray(); + JSONArray addresses = new JSONArray(); + JSONArray phones = new JSONArray(); + JSONArray emails = new JSONArray(); + JSONArray ims = new JSONArray(); + JSONArray websites = new JSONArray(); + JSONArray photos = new JSONArray(); + + // Column indices + int colContactId = c.getColumnIndex(ContactsContract.Data.CONTACT_ID); + int colRawContactId = c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID); + int colMimetype = c.getColumnIndex(ContactsContract.Data.MIMETYPE); + int colDisplayName = c.getColumnIndex(CommonDataKinds.StructuredName.DISPLAY_NAME); + int colNote = c.getColumnIndex(CommonDataKinds.Note.NOTE); + int colNickname = c.getColumnIndex(CommonDataKinds.Nickname.NAME); + int colEventType = c.getColumnIndex(CommonDataKinds.Event.TYPE); + + if (c.getCount() > 0) { + while (c.moveToNext() && (contacts.length() <= (limit - 1))) { + try { + contactId = c.getString(colContactId); + rawId = c.getString(colRawContactId); + + // If we are in the first row set the oldContactId + if (c.getPosition() == 0) { + oldContactId = contactId; + } + + // When the contact ID changes we need to push the Contact object + // to the array of contacts and create new objects. + if (!oldContactId.equals(contactId)) { + // Populate the Contact object with it's arrays + // and push the contact into the contacts array + contacts.put(populateContact(contact, organizations, addresses, phones, + emails, ims, websites, photos)); + + // Clean up the objects + contact = new JSONObject(); + organizations = new JSONArray(); + addresses = new JSONArray(); + phones = new JSONArray(); + emails = new JSONArray(); + ims = new JSONArray(); + websites = new JSONArray(); + photos = new JSONArray(); + + // Set newContact to true as we are starting to populate a new contact + newContact = true; + } + + // When we detect a new contact set the ID and display name. + // These fields are available in every row in the result set returned. + if (newContact) { + newContact = false; + contact.put("id", contactId); + contact.put("rawId", rawId); + } + + // Grab the mimetype of the current row as it will be used in a lot of comparisons + mimetype = c.getString(colMimetype); + + if (mimetype.equals(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) && isRequired("name", populate)) { + contact.put("displayName", c.getString(colDisplayName)); + } + + if (mimetype.equals(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + && isRequired("name", populate)) { + contact.put("name", nameQuery(c)); + } + else if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + && isRequired("phoneNumbers", populate)) { + phones.put(phoneQuery(c)); + } + else if (mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE) + && isRequired("emails", populate)) { + emails.put(emailQuery(c)); + } + else if (mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + && isRequired("addresses", populate)) { + addresses.put(addressQuery(c)); + } + else if (mimetype.equals(CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + && isRequired("organizations", populate)) { + organizations.put(organizationQuery(c)); + } + else if (mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE) + && isRequired("ims", populate)) { + ims.put(imQuery(c)); + } + else if (mimetype.equals(CommonDataKinds.Note.CONTENT_ITEM_TYPE) + && isRequired("note", populate)) { + contact.put("note", c.getString(colNote)); + } + else if (mimetype.equals(CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + && isRequired("nickname", populate)) { + contact.put("nickname", c.getString(colNickname)); + } + else if (mimetype.equals(CommonDataKinds.Website.CONTENT_ITEM_TYPE) + && isRequired("urls", populate)) { + websites.put(websiteQuery(c)); + } + else if (mimetype.equals(CommonDataKinds.Event.CONTENT_ITEM_TYPE)) { + if (isRequired("birthday", populate) && + CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(colEventType)) { + + Date birthday = getBirthday(c); + if (birthday != null) { + contact.put("birthday", birthday.getTime()); + } + } + } + else if (mimetype.equals(CommonDataKinds.Photo.CONTENT_ITEM_TYPE) + && isRequired("photos", populate)) { + JSONObject photo = photoQuery(c, contactId); + if (photo != null) { + photos.put(photo); + } + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + + // Set the old contact ID + oldContactId = contactId; + + } + + // Push the last contact into the contacts array + if (contacts.length() < limit) { + contacts.put(populateContact(contact, organizations, addresses, phones, + emails, ims, websites, photos)); + } + } + c.close(); + return contacts; + } + + /** + * Builds a where clause all all the ids passed into the method + * @param contactIds a set of unique contact ids + * @param searchTerm what to search for + * @return an object containing the selection and selection args + */ + private WhereOptions buildIdClause(Set contactIds, String searchTerm, boolean hasPhoneNumber) { + WhereOptions options = new WhereOptions(); + + // If the user is searching for every contact then short circuit the method + // and return a shorter where clause to be searched. + if (searchTerm.equals("%") && !hasPhoneNumber) { + options.setWhere("(" + ContactsContract.Data.CONTACT_ID + " LIKE ? )"); + options.setWhereArgs(new String[] { searchTerm }); + return options; + } + + // This clause means that there are specific ID's to be populated + Iterator it = contactIds.iterator(); + StringBuffer buffer = new StringBuffer("("); + + while (it.hasNext()) { + buffer.append("'" + it.next() + "'"); + if (it.hasNext()) { + buffer.append(","); + } + } + buffer.append(")"); + + options.setWhere(ContactsContract.Data.CONTACT_ID + " IN " + buffer.toString()); + options.setWhereArgs(null); + + return options; + } + + /** + * Create a new contact using a JSONObject to hold all the data. + * @param contact + * @param organizations array of organizations + * @param addresses array of addresses + * @param phones array of phones + * @param emails array of emails + * @param ims array of instant messenger addresses + * @param websites array of websites + * @param photos + * @return + */ + private JSONObject populateContact(JSONObject contact, JSONArray organizations, + JSONArray addresses, JSONArray phones, JSONArray emails, + JSONArray ims, JSONArray websites, JSONArray photos) { + try { + // Only return the array if it has at least one entry + if (organizations.length() > 0) { + contact.put("organizations", organizations); + } + if (addresses.length() > 0) { + contact.put("addresses", addresses); + } + if (phones.length() > 0) { + contact.put("phoneNumbers", phones); + } + if (emails.length() > 0) { + contact.put("emails", emails); + } + if (ims.length() > 0) { + contact.put("ims", ims); + } + if (websites.length() > 0) { + contact.put("urls", websites); + } + if (photos.length() > 0) { + contact.put("photos", photos); + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return contact; + } + + /** + * Take the search criteria passed into the method and create a SQL WHERE clause. + * @param fields the properties to search against + * @param searchTerm the string to search for + * @return an object containing the selection and selection args + */ + private WhereOptions buildWhereClause(JSONArray fields, String searchTerm, boolean hasPhoneNumber) { + + ArrayList where = new ArrayList(); + ArrayList whereArgs = new ArrayList(); + + WhereOptions options = new WhereOptions(); + + /* + * Special case where the user wants all fields returned + */ + if (isWildCardSearch(fields)) { + // Get all contacts with all properties + if ("%".equals(searchTerm) && !hasPhoneNumber) { + options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); + options.setWhereArgs(new String[] { searchTerm }); + return options; + } else { + // Get all contacts that match the filter but return all properties + where.add("(" + dbMap.get("displayName") + " LIKE ? )"); + whereArgs.add(searchTerm); + where.add("(" + dbMap.get("name") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("nickname") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Nickname.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("phoneNumbers") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Phone.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("emails") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Email.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("addresses") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("ims") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Im.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("organizations") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Organization.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("note") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Note.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("urls") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Website.CONTENT_ITEM_TYPE); + } + } + + /* + * Special case for when the user wants all the contacts but + */ + if ("%".equals(searchTerm) && !hasPhoneNumber) { + options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); + options.setWhereArgs(new String[] { searchTerm }); + return options; + }else if(!("%".equals(searchTerm))){ + String key; + try { + //Log.d(LOG_TAG, "How many fields do we have = " + fields.length()); + for (int i = 0; i < fields.length(); i++) { + key = fields.getString(i); + + if (key.equals("id")) { + where.add("(" + dbMap.get(key) + " = ? )"); + whereArgs.add(searchTerm.substring(1, searchTerm.length() - 1)); + } + else if (key.startsWith("displayName")) { + where.add("(" + dbMap.get(key) + " LIKE ? )"); + whereArgs.add(searchTerm); + } + else if (key.startsWith("name")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); + } + else if (key.startsWith("nickname")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Nickname.CONTENT_ITEM_TYPE); + } + else if (key.startsWith("phoneNumbers")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Phone.CONTENT_ITEM_TYPE); + } + else if (key.startsWith("emails")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Email.CONTENT_ITEM_TYPE); + } + else if (key.startsWith("addresses")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); + } + else if (key.startsWith("ims")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Im.CONTENT_ITEM_TYPE); + } + else if (key.startsWith("organizations")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Organization.CONTENT_ITEM_TYPE); + } + // else if (key.startsWith("birthday")) { + // where.add("(" + dbMap.get(key) + " LIKE ? AND " + // + ContactsContract.Data.MIMETYPE + " = ? )"); + // } + else if (key.startsWith("note")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Note.CONTENT_ITEM_TYPE); + } + else if (key.startsWith("urls")) { + where.add("(" + dbMap.get(key) + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(CommonDataKinds.Website.CONTENT_ITEM_TYPE); + } + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + + // Creating the where string + StringBuffer selection = new StringBuffer(); + for (int i = 0; i < where.size(); i++) { + selection.append(where.get(i)); + if (i != (where.size() - 1)) { + selection.append(" OR "); + } + } + + //Only contacts with phone number informed + if(hasPhoneNumber){ + if(where.size()>0){ + selection.insert(0,"("); + selection.append(") AND (" + ContactsContract.Contacts.HAS_PHONE_NUMBER + " = ?)"); + whereArgs.add("1"); + }else{ + selection.append("(" + ContactsContract.Contacts.HAS_PHONE_NUMBER + " = ?)"); + whereArgs.add("1"); + } + } + + options.setWhere(selection.toString()); + + // Creating the where args array + String[] selectionArgs = new String[whereArgs.size()]; + for (int i = 0; i < whereArgs.size(); i++) { + selectionArgs[i] = whereArgs.get(i); + } + options.setWhereArgs(selectionArgs); + + return options; + } + + /** + * If the user passes in the '*' wildcard character for search then they want all fields for each contact + * + * @param fields + * @return true if wildcard search requested, false otherwise + */ + private boolean isWildCardSearch(JSONArray fields) { + // Only do a wildcard search if we are passed ["*"] + if (fields.length() == 1) { + try { + if ("*".equals(fields.getString(0))) { + return true; + } + } catch (JSONException e) { + return false; + } + } + return false; + } + + /** + * Create a ContactOrganization JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactOrganization + */ + private JSONObject organizationQuery(Cursor cursor) { + JSONObject organization = new JSONObject(); + try { + organization.put("id", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Organization._ID))); + organization.put("pref", false); // Android does not store pref attribute + organization.put("type", getOrgType(cursor.getInt(cursor.getColumnIndex(CommonDataKinds.Organization.TYPE)))); + organization.put("department", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Organization.DEPARTMENT))); + organization.put("name", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Organization.COMPANY))); + organization.put("title", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Organization.TITLE))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return organization; + } + + /** + * Create a ContactAddress JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactAddress + */ + private JSONObject addressQuery(Cursor cursor) { + JSONObject address = new JSONObject(); + try { + address.put("id", cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredPostal._ID))); + address.put("pref", false); // Android does not store pref attribute + address.put("type", getAddressType(cursor.getInt(cursor.getColumnIndex(CommonDataKinds.Organization.TYPE)))); + address.put("formatted", cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS))); + address.put("streetAddress", cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredPostal.STREET))); + address.put("locality", cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredPostal.CITY))); + address.put("region", cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredPostal.REGION))); + address.put("postalCode", cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredPostal.POSTCODE))); + address.put("country", cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredPostal.COUNTRY))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return address; + } + + /** + * Create a ContactName JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactName + */ + private JSONObject nameQuery(Cursor cursor) { + JSONObject contactName = new JSONObject(); + try { + String familyName = cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredName.FAMILY_NAME)); + String givenName = cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredName.GIVEN_NAME)); + String middleName = cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredName.MIDDLE_NAME)); + String honorificPrefix = cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredName.PREFIX)); + String honorificSuffix = cursor.getString(cursor.getColumnIndex(CommonDataKinds.StructuredName.SUFFIX)); + + // Create the formatted name + StringBuffer formatted = new StringBuffer(""); + if (!TextUtils.isEmpty(honorificPrefix)) { + formatted.append(honorificPrefix + " "); + } + if (!TextUtils.isEmpty(givenName)) { + formatted.append(givenName + " "); + } + if (!TextUtils.isEmpty(middleName)) { + formatted.append(middleName + " "); + } + if (!TextUtils.isEmpty(familyName)) { + formatted.append(familyName); + } + if (!TextUtils.isEmpty(honorificSuffix)) { + formatted.append(" " + honorificSuffix); + } + if (TextUtils.isEmpty(formatted)) { + formatted = null; + } + + contactName.put("familyName", familyName); + contactName.put("givenName", givenName); + contactName.put("middleName", middleName); + contactName.put("honorificPrefix", honorificPrefix); + contactName.put("honorificSuffix", honorificSuffix); + contactName.put("formatted", formatted); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return contactName; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject phoneQuery(Cursor cursor) { + JSONObject phoneNumber = new JSONObject(); + try { + phoneNumber.put("id", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Phone._ID))); + phoneNumber.put("pref", false); // Android does not store pref attribute + phoneNumber.put("value", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Phone.NUMBER))); + phoneNumber.put("type", getPhoneType(cursor.getInt(cursor.getColumnIndex(CommonDataKinds.Phone.TYPE)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } catch (Exception excp) { + Log.e(LOG_TAG, excp.getMessage(), excp); + } + return phoneNumber; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject emailQuery(Cursor cursor) { + JSONObject email = new JSONObject(); + try { + email.put("id", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Email._ID))); + email.put("pref", false); // Android does not store pref attribute + email.put("value", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Email.DATA))); + email.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(CommonDataKinds.Email.TYPE)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return email; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject imQuery(Cursor cursor) { + JSONObject im = new JSONObject(); + try { + im.put("id", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Im._ID))); + im.put("pref", false); // Android does not store pref attribute + im.put("value", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Im.DATA))); + im.put("type", getImType(cursor.getString(cursor.getColumnIndex(CommonDataKinds.Im.PROTOCOL)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return im; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject websiteQuery(Cursor cursor) { + JSONObject website = new JSONObject(); + try { + website.put("id", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Website._ID))); + website.put("pref", false); // Android does not store pref attribute + website.put("value", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Website.URL))); + website.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(CommonDataKinds.Website.TYPE)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return website; + } + + /** + * Create a ContactField JSONObject + * @param contactId + * @return a JSONObject representing a ContactField + */ + private JSONObject photoQuery(Cursor cursor, String contactId) { + JSONObject photo = new JSONObject(); + Cursor photoCursor = null; + try { + photo.put("id", cursor.getString(cursor.getColumnIndex(CommonDataKinds.Photo._ID))); + photo.put("pref", false); + photo.put("type", "url"); + Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, (Long.valueOf(contactId))); + Uri photoUri = Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); + photo.put("value", photoUri.toString()); + + // Query photo existance + photoCursor = mApp.getActivity().getContentResolver().query(photoUri, new String[] {ContactsContract.Contacts.Photo.PHOTO}, null, null, null); + if (photoCursor == null) return null; + if (!photoCursor.moveToFirst()) { + photoCursor.close(); + return null; + } + photoCursor.close(); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } catch (SQLiteException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } finally { + if(photoCursor != null && !photoCursor.isClosed()) { + photoCursor.close(); + } + } + return photo; + } + + @Override + /** + * This method will save a contact object into the devices contacts database. + * + * @param contact the contact to be saved. + * @returns the id if the contact is successfully saved, null otherwise. + */ + public String save(JSONObject contact) { + AccountManager mgr = AccountManager.get(mApp.getActivity()); + Account[] accounts = mgr.getAccounts(); + String accountName = null; + String accountType = null; + + if (accounts.length == 1) { + accountName = accounts[0].name; + accountType = accounts[0].type; + } + else if (accounts.length > 1) { + for (Account a : accounts) { + if (a.type.contains("eas") && a.name.matches(EMAIL_REGEXP)) /*Exchange ActiveSync*/{ + accountName = a.name; + accountType = a.type; + break; + } + } + if (accountName == null) { + for (Account a : accounts) { + if (a.type.contains("com.google") && a.name.matches(EMAIL_REGEXP)) /*Google sync provider*/{ + accountName = a.name; + accountType = a.type; + break; + } + } + } + if (accountName == null) { + for (Account a : accounts) { + if (a.name.matches(EMAIL_REGEXP)) /*Last resort, just look for an email address...*/{ + accountName = a.name; + accountType = a.type; + break; + } + } + } + } + + String id = getJsonString(contact, "id"); + if (id == null) { + // Create new contact + return createNewContact(contact, accountType, accountName); + } else { + // Modify existing contact + return modifyContact(id, contact, accountType, accountName); + } + } + + /** + * Creates a new contact and stores it in the database + * + * @param id the raw contact id which is required for linking items to the contact + * @param contact the contact to be saved + * @param account the account to be saved under + */ + private String modifyContact(String id, JSONObject contact, String accountType, String accountName) { + // Get the RAW_CONTACT_ID which is needed to insert new values in an already existing contact. + // But not needed to update existing values. + String rawId = getJsonString(contact, "rawId"); + + // Create a list of attributes to add to the contact database + ArrayList ops = new ArrayList(); + + //Add contact type + ops.add(ContentProviderOperation.newUpdate(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) + .build()); + + // Modify name + JSONObject name; + try { + String displayName = getJsonString(contact, "displayName"); + name = contact.getJSONObject("name"); + if (displayName != null || name != null) { + ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { id, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE }); + + if (displayName != null) { + builder.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, displayName); + } + + String familyName = getJsonString(name, "familyName"); + if (familyName != null) { + builder.withValue(CommonDataKinds.StructuredName.FAMILY_NAME, familyName); + } + String middleName = getJsonString(name, "middleName"); + if (middleName != null) { + builder.withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, middleName); + } + String givenName = getJsonString(name, "givenName"); + if (givenName != null) { + builder.withValue(CommonDataKinds.StructuredName.GIVEN_NAME, givenName); + } + String honorificPrefix = getJsonString(name, "honorificPrefix"); + if (honorificPrefix != null) { + builder.withValue(CommonDataKinds.StructuredName.PREFIX, honorificPrefix); + } + String honorificSuffix = getJsonString(name, "honorificSuffix"); + if (honorificSuffix != null) { + builder.withValue(CommonDataKinds.StructuredName.SUFFIX, honorificSuffix); + } + + ops.add(builder.build()); + } + } catch (JSONException e1) { + Log.d(LOG_TAG, "Could not get name"); + } + + // Modify phone numbers + JSONArray phones = null; + try { + phones = contact.getJSONArray("phoneNumbers"); + if (phones != null) { + // Delete all the phones + if (phones.length() == 0) { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { "" + rawId, CommonDataKinds.Phone.CONTENT_ITEM_TYPE }) + .build()); + } + // Modify or add a phone + else { + for (int i = 0; i < phones.length(); i++) { + JSONObject phone = (JSONObject) phones.get(i); + String phoneId = getJsonString(phone, "id"); + // This is a new phone so do a DB insert + if (phoneId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE); + contentValues.put(CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")); + contentValues.put(CommonDataKinds.Phone.TYPE, getPhoneType(getJsonString(phone, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing phone so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.Phone._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { phoneId, CommonDataKinds.Phone.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")) + .withValue(CommonDataKinds.Phone.TYPE, getPhoneType(getJsonString(phone, "type"))) + .build()); + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get phone numbers"); + } + + // Modify emails + JSONArray emails = null; + try { + emails = contact.getJSONArray("emails"); + if (emails != null) { + // Delete all the emails + if (emails.length() == 0) { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { "" + rawId, CommonDataKinds.Email.CONTENT_ITEM_TYPE }) + .build()); + } + // Modify or add a email + else { + for (int i = 0; i < emails.length(); i++) { + JSONObject email = (JSONObject) emails.get(i); + String emailId = getJsonString(email, "id"); + // This is a new email so do a DB insert + if (emailId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE); + contentValues.put(CommonDataKinds.Email.DATA, getJsonString(email, "value")); + contentValues.put(CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing email so do a DB update + else { + String emailValue=getJsonString(email, "value"); + if(!emailValue.isEmpty()) { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.Email._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { emailId, CommonDataKinds.Email.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.Email.DATA, getJsonString(email, "value")) + .withValue(CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type"))) + .build()); + } else { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.Email._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { emailId, CommonDataKinds.Email.CONTENT_ITEM_TYPE }) + .build()); + } + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get emails"); + } + + // Modify addresses + JSONArray addresses = null; + try { + addresses = contact.getJSONArray("addresses"); + if (addresses != null) { + // Delete all the addresses + if (addresses.length() == 0) { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { "" + rawId, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE }) + .build()); + } + // Modify or add a address + else { + for (int i = 0; i < addresses.length(); i++) { + JSONObject address = (JSONObject) addresses.get(i); + String addressId = getJsonString(address, "id"); + // This is a new address so do a DB insert + if (addressId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))); + contentValues.put(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")); + contentValues.put(CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")); + contentValues.put(CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")); + contentValues.put(CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")); + contentValues.put(CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")); + contentValues.put(CommonDataKinds.StructuredPostal.COUNTRY, getJsonString(address, "country")); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing address so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.StructuredPostal._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { addressId, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))) + .withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")) + .withValue(CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")) + .withValue(CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")) + .withValue(CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")) + .withValue(CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")) + .withValue(CommonDataKinds.StructuredPostal.COUNTRY, getJsonString(address, "country")) + .build()); + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get addresses"); + } + + // Modify organizations + JSONArray organizations = null; + try { + organizations = contact.getJSONArray("organizations"); + if (organizations != null) { + // Delete all the organizations + if (organizations.length() == 0) { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { "" + rawId, CommonDataKinds.Organization.CONTENT_ITEM_TYPE }) + .build()); + } + // Modify or add a organization + else { + for (int i = 0; i < organizations.length(); i++) { + JSONObject org = (JSONObject) organizations.get(i); + String orgId = getJsonString(org, "id"); + // This is a new organization so do a DB insert + if (orgId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE); + contentValues.put(CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))); + contentValues.put(CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")); + contentValues.put(CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")); + contentValues.put(CommonDataKinds.Organization.TITLE, getJsonString(org, "title")); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing organization so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.Organization._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { orgId, CommonDataKinds.Organization.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))) + .withValue(CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")) + .withValue(CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")) + .withValue(CommonDataKinds.Organization.TITLE, getJsonString(org, "title")) + .build()); + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get organizations"); + } + + // Modify IMs + JSONArray ims = null; + try { + ims = contact.getJSONArray("ims"); + if (ims != null) { + // Delete all the ims + if (ims.length() == 0) { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { "" + rawId, CommonDataKinds.Im.CONTENT_ITEM_TYPE }) + .build()); + } + // Modify or add a im + else { + for (int i = 0; i < ims.length(); i++) { + JSONObject im = (JSONObject) ims.get(i); + String imId = getJsonString(im, "id"); + // This is a new IM so do a DB insert + if (imId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Im.CONTENT_ITEM_TYPE); + contentValues.put(CommonDataKinds.Im.DATA, getJsonString(im, "value")); + contentValues.put(CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing IM so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.Im._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { imId, CommonDataKinds.Im.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.Im.DATA, getJsonString(im, "value")) + .withValue(CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type"))) + .build()); + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get emails"); + } + + // Modify note + String note = getJsonString(contact, "note"); + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { id, CommonDataKinds.Note.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.Note.NOTE, note) + .build()); + + // Modify nickname + String nickname = getJsonString(contact, "nickname"); + if (nickname != null) { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { id, CommonDataKinds.Nickname.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.Nickname.NAME, nickname) + .build()); + } + + // Modify urls + JSONArray websites = null; + try { + websites = contact.getJSONArray("urls"); + if (websites != null) { + // Delete all the websites + if (websites.length() == 0) { + Log.d(LOG_TAG, "This means we should be deleting all the phone numbers."); + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { "" + rawId, CommonDataKinds.Website.CONTENT_ITEM_TYPE }) + .build()); + } + // Modify or add a website + else { + for (int i = 0; i < websites.length(); i++) { + JSONObject website = (JSONObject) websites.get(i); + String websiteId = getJsonString(website, "id"); + // This is a new website so do a DB insert + if (websiteId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Website.CONTENT_ITEM_TYPE); + contentValues.put(CommonDataKinds.Website.DATA, getJsonString(website, "value")); + contentValues.put(CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing website so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.Website._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { websiteId, CommonDataKinds.Website.CONTENT_ITEM_TYPE }) + .withValue(CommonDataKinds.Website.DATA, getJsonString(website, "value")) + .withValue(CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))) + .build()); + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get websites"); + } + + // Modify birthday + Date birthday = getBirthday(contact); + if (birthday != null) { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=? AND " + + CommonDataKinds.Event.TYPE + "=?", + new String[]{id, CommonDataKinds.Event.CONTENT_ITEM_TYPE, "" + CommonDataKinds.Event.TYPE_BIRTHDAY}) + .withValue(CommonDataKinds.Event.TYPE, CommonDataKinds.Event.TYPE_BIRTHDAY) + .withValue(CommonDataKinds.Event.START_DATE, birthday.toString()) + .build()); + } + + // Modify photos + JSONArray photos = null; + try { + photos = contact.getJSONArray("photos"); + if (photos != null) { + // Delete all the photos + if (photos.length() == 0) { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { "" + rawId, CommonDataKinds.Photo.CONTENT_ITEM_TYPE }) + .build()); + } + // Modify or add a photo + else { + for (int i = 0; i < photos.length(); i++) { + JSONObject photo = (JSONObject) photos.get(i); + String photoId = getJsonString(photo, "id"); + byte[] bytes = getPhotoBytes(getJsonString(photo, "value")); + // This is a new photo so do a DB insert + if (photoId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1); + contentValues.put(CommonDataKinds.Photo.PHOTO, bytes); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing photo so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(CommonDataKinds.Photo._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { photoId, CommonDataKinds.Photo.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1) + .withValue(CommonDataKinds.Photo.PHOTO, bytes) + .build()); + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get photos"); + } + + boolean retVal = true; + + //Modify contact + try { + mApp.getActivity().getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + } catch (RemoteException e) { + Log.e(LOG_TAG, e.getMessage(), e); + Log.e(LOG_TAG, Log.getStackTraceString(e), e); + retVal = false; + } catch (OperationApplicationException e) { + Log.e(LOG_TAG, e.getMessage(), e); + Log.e(LOG_TAG, Log.getStackTraceString(e), e); + retVal = false; + } + + // if the save was a success return the contact ID + if (retVal) { + return rawId; + } else { + return null; + } + } + + /** + * Add a website to a list of database actions to be performed + * + * @param ops the list of database actions + * @param website the item to be inserted + */ + private void insertWebsite(ArrayList ops, + JSONObject website) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Website.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Website.DATA, getJsonString(website, "value")) + .withValue(CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))) + .build()); + } + + /** + * Add an im to a list of database actions to be performed + * + * @param ops the list of database actions + * @param im the item to be inserted + */ + private void insertIm(ArrayList ops, JSONObject im) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Im.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Im.DATA, getJsonString(im, "value")) + .withValue(CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type"))) + .build()); + } + + /** + * Add an organization to a list of database actions to be performed + * + * @param ops the list of database actions + * @param org the item to be inserted + */ + private void insertOrganization(ArrayList ops, + JSONObject org) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))) + .withValue(CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")) + .withValue(CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")) + .withValue(CommonDataKinds.Organization.TITLE, getJsonString(org, "title")) + .build()); + } + + /** + * Add an address to a list of database actions to be performed + * + * @param ops the list of database actions + * @param address the item to be inserted + */ + private void insertAddress(ArrayList ops, + JSONObject address) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))) + .withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")) + .withValue(CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")) + .withValue(CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")) + .withValue(CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")) + .withValue(CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")) + .withValue(CommonDataKinds.StructuredPostal.COUNTRY, getJsonString(address, "country")) + .build()); + } + + /** + * Add an email to a list of database actions to be performed + * + * @param ops the list of database actions + * @param email the item to be inserted + */ + private void insertEmail(ArrayList ops, + JSONObject email) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Email.DATA, getJsonString(email, "value")) + .withValue(CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type"))) + .build()); + } + + /** + * Add a phone to a list of database actions to be performed + * + * @param ops the list of database actions + * @param phone the item to be inserted + */ + private void insertPhone(ArrayList ops, + JSONObject phone) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")) + .withValue(CommonDataKinds.Phone.TYPE, getPhoneType(getJsonString(phone, "type"))) + .build()); + } + + /** + * Add a phone to a list of database actions to be performed + * + * @param ops the list of database actions + * @param phone the item to be inserted + */ + private void insertPhoto(ArrayList ops, + JSONObject photo) { + byte[] bytes = getPhotoBytes(getJsonString(photo, "value")); + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Photo.PHOTO, bytes) + .build()); + } + + /** + * Gets the raw bytes from the supplied filename + * + * @param filename the file to read the bytes from + * @return a byte array + * @throws IOException + */ + private byte[] getPhotoBytes(String filename) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try { + int bytesRead = 0; + long totalBytesRead = 0; + byte[] data = new byte[8192]; + InputStream in = getPathFromUri(filename); + + while ((bytesRead = in.read(data, 0, data.length)) != -1 && totalBytesRead <= MAX_PHOTO_SIZE) { + buffer.write(data, 0, bytesRead); + totalBytesRead += bytesRead; + } + + in.close(); + buffer.flush(); + } catch (FileNotFoundException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } catch (IOException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return buffer.toByteArray(); + } + + /** + * Get an input stream based on file path or uri content://, http://, file:// + * + * @param path path to file + * @return an input stream + * @throws IOException + */ + private InputStream getPathFromUri(String path) throws IOException { + if (path.startsWith("content:")) { + Uri uri = Uri.parse(path); + return mApp.getActivity().getContentResolver().openInputStream(uri); + } + + if (path.startsWith(ASSET_URL_PREFIX)) { + String assetRelativePath = path.replace(ASSET_URL_PREFIX, ""); + return mApp.getActivity().getAssets().open(assetRelativePath); + } + + if (path.startsWith("http:") || path.startsWith("https:") || path.startsWith("file:")) { + URL url = new URL(path); + return url.openStream(); + } + + return new FileInputStream(path); + } + + /** + * Creates a new contact and stores it in the database + * + * @param contact the contact to be saved + * @param account the account to be saved under + */ + private String createNewContact(JSONObject contact, String accountType, String accountName) { + // Create a list of attributes to add to the contact database + ArrayList ops = new ArrayList(); + + //Add contact type + ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) + .build()); + + // Add name + JSONObject name = contact.optJSONObject("name"); + String displayName = getJsonString(contact, "displayName"); + if (displayName != null || name != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, displayName) + .withValue(CommonDataKinds.StructuredName.FAMILY_NAME, getJsonString(name, "familyName")) + .withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, getJsonString(name, "middleName")) + .withValue(CommonDataKinds.StructuredName.GIVEN_NAME, getJsonString(name, "givenName")) + .withValue(CommonDataKinds.StructuredName.PREFIX, getJsonString(name, "honorificPrefix")) + .withValue(CommonDataKinds.StructuredName.SUFFIX, getJsonString(name, "honorificSuffix")) + .build()); + } else { + Log.d(LOG_TAG, "Both \"name\" and \"displayName\" properties are empty"); + } + + //Add phone numbers + JSONArray phones = null; + try { + phones = contact.getJSONArray("phoneNumbers"); + if (phones != null) { + for (int i = 0; i < phones.length(); i++) { + JSONObject phone = (JSONObject) phones.get(i); + insertPhone(ops, phone); + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get phone numbers"); + } + + // Add emails + JSONArray emails = null; + try { + emails = contact.getJSONArray("emails"); + if (emails != null) { + for (int i = 0; i < emails.length(); i++) { + JSONObject email = (JSONObject) emails.get(i); + insertEmail(ops, email); + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get emails"); + } + + // Add addresses + JSONArray addresses = null; + try { + addresses = contact.getJSONArray("addresses"); + if (addresses != null) { + for (int i = 0; i < addresses.length(); i++) { + JSONObject address = (JSONObject) addresses.get(i); + insertAddress(ops, address); + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get addresses"); + } + + // Add organizations + JSONArray organizations = null; + try { + organizations = contact.getJSONArray("organizations"); + if (organizations != null) { + for (int i = 0; i < organizations.length(); i++) { + JSONObject org = (JSONObject) organizations.get(i); + insertOrganization(ops, org); + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get organizations"); + } + + // Add IMs + JSONArray ims = null; + try { + ims = contact.getJSONArray("ims"); + if (ims != null) { + for (int i = 0; i < ims.length(); i++) { + JSONObject im = (JSONObject) ims.get(i); + insertIm(ops, im); + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get emails"); + } + + // Add note + String note = getJsonString(contact, "note"); + if (note != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Note.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Note.NOTE, note) + .build()); + } + + // Add nickname + String nickname = getJsonString(contact, "nickname"); + if (nickname != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Nickname.NAME, nickname) + .build()); + } + + // Add urls + JSONArray websites = null; + try { + websites = contact.getJSONArray("urls"); + if (websites != null) { + for (int i = 0; i < websites.length(); i++) { + JSONObject website = (JSONObject) websites.get(i); + insertWebsite(ops, website); + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get websites"); + } + + // Add birthday + Date birthday = getBirthday(contact); + if (birthday != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Event.TYPE, CommonDataKinds.Event.TYPE_BIRTHDAY) + .withValue(CommonDataKinds.Event.START_DATE, birthday.toString()) + .build()); + } + + // Add photos + JSONArray photos = null; + try { + photos = contact.getJSONArray("photos"); + if (photos != null) { + for (int i = 0; i < photos.length(); i++) { + JSONObject photo = (JSONObject) photos.get(i); + insertPhoto(ops, photo); + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get photos"); + } + + String newId = null; + //Add contact + try { + ContentProviderResult[] cpResults = mApp.getActivity().getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + if (cpResults.length >= 0) { + newId = cpResults[0].uri.getLastPathSegment(); + } + } catch (RemoteException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } catch (OperationApplicationException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return newId; + } + + @Override + /** + * This method will remove a Contact from the database based on ID. + * @param id the unique ID of the contact to remove + */ + public boolean remove(String id) { + int result = 0; + Cursor cursor = mApp.getActivity().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, + null, + ContactsContract.Contacts._ID + " = ?", + new String[] { id }, null); + + if (cursor.getCount() == 1) { + cursor.moveToFirst(); + String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)); + Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey); + result = mApp.getActivity().getContentResolver().delete(uri, null, null); + } else { + Log.d(LOG_TAG, "Could not find contact with ID"); + } + + return (result > 0) ? true : false; + } + + /** + * Gets birthday date from contact JSON object + * @param contact an object to get birthday from + * @return birthday or null, if the field isn't present or + * is malformed in the contact + */ + private Date getBirthday(JSONObject contact) { + try { + Long timestamp = contact.getLong("birthday"); + return new Date(timestamp); + } catch (JSONException e) { + Log.e(LOG_TAG, "Could not get birthday from JSON object", e); + return null; + } + } + + /** + * Gets birthday from contacts database cursor object + * @param c cursor for the contact + * @return birthday or null, if birthday column is empty or + * the value can't be parsed into valid date object + */ + private Date getBirthday(Cursor c) { + int colBirthday = c.getColumnIndex(CommonDataKinds.Event.START_DATE); + + try { + return Date.valueOf(c.getString(colBirthday)); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "Failed to get birthday for contact from cursor", e); + return null; + } + } + + /************************************************************************** + * + * All methods below this comment are used to convert from JavaScript + * text types to Android integer types and vice versa. + * + *************************************************************************/ + + /** + * Converts a string from the W3C Contact API to it's Android int value. + * @param string + * @return Android int value + */ + private int getPhoneType(String string) { + + int type = Phone.TYPE_OTHER; + + if (string != null) { + String lowerType = string.toLowerCase(Locale.getDefault()); + + if ("home".equals(lowerType)) { + return Phone.TYPE_HOME; + } + else if ("mobile".equals(lowerType)) { + return Phone.TYPE_MOBILE; + } + else if ("work".equals(lowerType)) { + return Phone.TYPE_WORK; + } + else if ("work fax".equals(lowerType)) { + return Phone.TYPE_FAX_WORK; + } + else if ("home fax".equals(lowerType)) { + return Phone.TYPE_FAX_HOME; + } + else if ("fax".equals(lowerType)) { + return Phone.TYPE_FAX_WORK; + } + else if ("pager".equals(lowerType)) { + return Phone.TYPE_PAGER; + } + else if ("other".equals(lowerType)) { + return Phone.TYPE_OTHER; + } + else if ("car".equals(lowerType)) { + return Phone.TYPE_CAR; + } + else if ("company main".equals(lowerType)) { + return Phone.TYPE_COMPANY_MAIN; + } + else if ("isdn".equals(lowerType)) { + return Phone.TYPE_ISDN; + } + else if ("main".equals(lowerType)) { + return Phone.TYPE_MAIN; + } + else if ("other fax".equals(lowerType)) { + return Phone.TYPE_OTHER_FAX; + } + else if ("radio".equals(lowerType)) { + return Phone.TYPE_RADIO; + } + else if ("telex".equals(lowerType)) { + return Phone.TYPE_TELEX; + } + else if ("work mobile".equals(lowerType)) { + return Phone.TYPE_WORK_MOBILE; + } + else if ("work pager".equals(lowerType)) { + return Phone.TYPE_WORK_PAGER; + } + else if ("assistant".equals(lowerType)) { + return Phone.TYPE_ASSISTANT; + } + else if ("mms".equals(lowerType)) { + return Phone.TYPE_MMS; + } + else if ("callback".equals(lowerType)) { + return Phone.TYPE_CALLBACK; + } + else if ("tty ttd".equals(lowerType)) { + return Phone.TYPE_TTY_TDD; + } + else if ("custom".equals(lowerType)) { + return Phone.TYPE_CUSTOM; + } + } + return type; + } + + /** + * getPhoneType converts an Android phone type into a string + * @param type + * @return phone type as string. + */ + private String getPhoneType(int type) { + String stringType; + + switch (type) { + case Phone.TYPE_CUSTOM: + stringType = "custom"; + break; + case Phone.TYPE_FAX_HOME: + stringType = "home fax"; + break; + case Phone.TYPE_FAX_WORK: + stringType = "work fax"; + break; + case Phone.TYPE_HOME: + stringType = "home"; + break; + case Phone.TYPE_MOBILE: + stringType = "mobile"; + break; + case Phone.TYPE_PAGER: + stringType = "pager"; + break; + case Phone.TYPE_WORK: + stringType = "work"; + break; + case Phone.TYPE_CALLBACK: + stringType = "callback"; + break; + case Phone.TYPE_CAR: + stringType = "car"; + break; + case Phone.TYPE_COMPANY_MAIN: + stringType = "company main"; + break; + case Phone.TYPE_OTHER_FAX: + stringType = "other fax"; + break; + case Phone.TYPE_RADIO: + stringType = "radio"; + break; + case Phone.TYPE_TELEX: + stringType = "telex"; + break; + case Phone.TYPE_TTY_TDD: + stringType = "tty tdd"; + break; + case Phone.TYPE_WORK_MOBILE: + stringType = "work mobile"; + break; + case Phone.TYPE_WORK_PAGER: + stringType = "work pager"; + break; + case Phone.TYPE_ASSISTANT: + stringType = "assistant"; + break; + case Phone.TYPE_MMS: + stringType = "mms"; + break; + case Phone.TYPE_ISDN: + stringType = "isdn"; + break; + case Phone.TYPE_OTHER: + default: + stringType = "other"; + break; + } + return stringType; + } + + /** + * Converts a string from the W3C Contact API to it's Android int value. + * @param string + * @return Android int value + */ + private int getContactType(String string) { + int type = CommonDataKinds.Email.TYPE_OTHER; + if (string != null) { + + String lowerType = string.toLowerCase(Locale.getDefault()); + + if ("home".equals(lowerType)) { + return CommonDataKinds.Email.TYPE_HOME; + } + else if ("work".equals(lowerType)) { + return CommonDataKinds.Email.TYPE_WORK; + } + else if ("other".equals(lowerType)) { + return CommonDataKinds.Email.TYPE_OTHER; + } + else if ("mobile".equals(lowerType)) { + return CommonDataKinds.Email.TYPE_MOBILE; + } + else if ("custom".equals(lowerType)) { + return CommonDataKinds.Email.TYPE_CUSTOM; + } + } + return type; + } + + /** + * getPhoneType converts an Android phone type into a string + * @param type + * @return phone type as string. + */ + private String getContactType(int type) { + String stringType; + switch (type) { + case CommonDataKinds.Email.TYPE_CUSTOM: + stringType = "custom"; + break; + case CommonDataKinds.Email.TYPE_HOME: + stringType = "home"; + break; + case CommonDataKinds.Email.TYPE_WORK: + stringType = "work"; + break; + case CommonDataKinds.Email.TYPE_MOBILE: + stringType = "mobile"; + break; + case CommonDataKinds.Email.TYPE_OTHER: + default: + stringType = "other"; + break; + } + return stringType; + } + + /** + * Converts a string from the W3C Contact API to it's Android int value. + * @param string + * @return Android int value + */ + private int getOrgType(String string) { + int type = CommonDataKinds.Organization.TYPE_OTHER; + if (string != null) { + String lowerType = string.toLowerCase(Locale.getDefault()); + if ("work".equals(lowerType)) { + return CommonDataKinds.Organization.TYPE_WORK; + } + else if ("other".equals(lowerType)) { + return CommonDataKinds.Organization.TYPE_OTHER; + } + else if ("custom".equals(lowerType)) { + return CommonDataKinds.Organization.TYPE_CUSTOM; + } + } + return type; + } + + /** + * getPhoneType converts an Android phone type into a string + * @param type + * @return phone type as string. + */ + private String getOrgType(int type) { + String stringType; + switch (type) { + case CommonDataKinds.Organization.TYPE_CUSTOM: + stringType = "custom"; + break; + case CommonDataKinds.Organization.TYPE_WORK: + stringType = "work"; + break; + case CommonDataKinds.Organization.TYPE_OTHER: + default: + stringType = "other"; + break; + } + return stringType; + } + + /** + * Converts a string from the W3C Contact API to it's Android int value. + * @param string + * @return Android int value + */ + private int getAddressType(String string) { + int type = CommonDataKinds.StructuredPostal.TYPE_OTHER; + if (string != null) { + String lowerType = string.toLowerCase(Locale.getDefault()); + + if ("work".equals(lowerType)) { + return CommonDataKinds.StructuredPostal.TYPE_WORK; + } + else if ("other".equals(lowerType)) { + return CommonDataKinds.StructuredPostal.TYPE_OTHER; + } + else if ("home".equals(lowerType)) { + return CommonDataKinds.StructuredPostal.TYPE_HOME; + } + } + return type; + } + + /** + * getPhoneType converts an Android phone type into a string + * @param type + * @return phone type as string. + */ + private String getAddressType(int type) { + String stringType; + switch (type) { + case CommonDataKinds.StructuredPostal.TYPE_HOME: + stringType = "home"; + break; + case CommonDataKinds.StructuredPostal.TYPE_WORK: + stringType = "work"; + break; + case CommonDataKinds.StructuredPostal.TYPE_OTHER: + default: + stringType = "other"; + break; + } + return stringType; + } + + /** + * Converts a string from the W3C Contact API to it's Android int value. + * @param string + * @return Android int value + */ + private int getImType(String string) { + int type = CommonDataKinds.Im.PROTOCOL_CUSTOM; + if (string != null) { + String lowerType = string.toLowerCase(Locale.getDefault()); + + if ("aim".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_AIM; + } + else if ("google talk".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK; + } + else if ("icq".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_ICQ; + } + else if ("jabber".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_JABBER; + } + else if ("msn".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_MSN; + } + else if ("netmeeting".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_NETMEETING; + } + else if ("qq".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_QQ; + } + else if ("skype".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_SKYPE; + } + else if ("yahoo".equals(lowerType)) { + return CommonDataKinds.Im.PROTOCOL_YAHOO; + } + } + return type; + } + + /** + * getPhoneType converts an Android phone type into a string + * @param type + * @return phone type as string. + */ + @SuppressWarnings("unused") + private String getImType(int type) { + String stringType; + switch (type) { + case CommonDataKinds.Im.PROTOCOL_AIM: + stringType = "AIM"; + break; + case CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK: + stringType = "Google Talk"; + break; + case CommonDataKinds.Im.PROTOCOL_ICQ: + stringType = "ICQ"; + break; + case CommonDataKinds.Im.PROTOCOL_JABBER: + stringType = "Jabber"; + break; + case CommonDataKinds.Im.PROTOCOL_MSN: + stringType = "MSN"; + break; + case CommonDataKinds.Im.PROTOCOL_NETMEETING: + stringType = "NetMeeting"; + break; + case CommonDataKinds.Im.PROTOCOL_QQ: + stringType = "QQ"; + break; + case CommonDataKinds.Im.PROTOCOL_SKYPE: + stringType = "Skype"; + break; + case CommonDataKinds.Im.PROTOCOL_YAHOO: + stringType = "Yahoo"; + break; + default: + stringType = "custom"; + break; + } + return stringType; + } + +}