diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b61b2c2..80b2d2d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,10 @@ + + + + } phoneNumbers array of phone numbers +* @param {Array.} emails array of email addresses +* @param {Array.} addresses array of addresses +* @param {Array.} ims instant messaging user ids +* @param {Array.} organizations +* @param {DOMString} birthday contact's birthday +* @param {DOMString} note user notes about contact +* @param {Array.} photos +* @param {Array.} categories +* @param {Array.} urls contact's web sites +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, birthday, note, photos, categories, urls) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.birthday = birthday || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] +}; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + argscheck.checkArgs('FF', 'Contact.remove', arguments); + var fail = errorCB && function(code) { + errorCB(new ContactError(code)); + }; + if (this.id === null) { + fail(ContactError.UNKNOWN_ERROR); + } + else { + exec(successCB, fail, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = utils.clone(this); + clonedContact.id = null; + clonedContact.rawId = null; + + function nullIds(arr) { + if (arr) { + for (var i = 0; i < arr.length; ++i) { + arr[i].id = null; + } + } + } + + // Loop through and clear out any id's in phones, emails, etc. + nullIds(clonedContact.phoneNumbers); + nullIds(clonedContact.emails); + nullIds(clonedContact.addresses); + nullIds(clonedContact.ims); + nullIds(clonedContact.organizations); + nullIds(clonedContact.categories); + nullIds(clonedContact.photos); + nullIds(clonedContact.urls); + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + argscheck.checkArgs('FFO', 'Contact.save', arguments); + var fail = errorCB && function(code) { + errorCB(new ContactError(code)); + }; + var success = function(result) { + if (result) { + if (successCB) { + var fullContact = require('./contacts').create(result); + successCB(convertIn(fullContact)); + } + } + else { + // no Entry object returned + fail(ContactError.UNKNOWN_ERROR); + } + }; + var dupContact = convertOut(utils.clone(this)); + exec(success, fail, "Contacts", "save", [dupContact]); +}; + + +module.exports = Contact; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactAddress.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactAddress.js new file mode 100644 index 0000000..a6246de --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactAddress.js @@ -0,0 +1,48 @@ +cordova.define("cordova-plugin-contacts.ContactAddress", function(require, exports, module) { /* + * + * 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. + * +*/ + +/** +* Contact address. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted // NOTE: not a W3C standard +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ + +var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +module.exports = ContactAddress; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactError.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactError.js new file mode 100644 index 0000000..723aa16 --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactError.js @@ -0,0 +1,44 @@ +cordova.define("cordova-plugin-contacts.ContactError", function(require, exports, module) { /* + * + * 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. + * +*/ + +/** + * ContactError. + * An error code assigned by an implementation when an error has occurred + * @constructor + */ +var ContactError = function(err) { + this.code = (typeof err != 'undefined' ? err : null); +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.TIMEOUT_ERROR = 2; +ContactError.PENDING_OPERATION_ERROR = 3; +ContactError.IO_ERROR = 4; +ContactError.NOT_SUPPORTED_ERROR = 5; +ContactError.PERMISSION_DENIED_ERROR = 20; + +module.exports = ContactError; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactField.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactField.js new file mode 100644 index 0000000..589f585 --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactField.js @@ -0,0 +1,39 @@ +cordova.define("cordova-plugin-contacts.ContactField", function(require, exports, module) { /* + * + * 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. + * +*/ + +/** +* Generic contact field. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = (type && type.toString()) || null; + this.value = (value && value.toString()) || null; + this.pref = (typeof pref != 'undefined' ? pref : false); +}; + +module.exports = ContactField; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactFieldType.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactFieldType.js new file mode 100644 index 0000000..c364ed6 --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactFieldType.js @@ -0,0 +1,57 @@ +cordova.define("cordova-plugin-contacts.ContactFieldType", function(require, exports, module) { /* + * + * 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. + * +*/ + + // Possible field names for various platforms. + // Some field names are platform specific + + var fieldType = { + addresses: "addresses", + birthday: "birthday", + categories: "categories", + country: "country", + department: "department", + displayName: "displayName", + emails: "emails", + familyName: "familyName", + formatted: "formatted", + givenName: "givenName", + honorificPrefix: "honorificPrefix", + honorificSuffix: "honorificSuffix", + id: "id", + ims: "ims", + locality: "locality", + middleName: "middleName", + name: "name", + nickname: "nickname", + note: "note", + organizations: "organizations", + phoneNumbers: "phoneNumbers", + photos: "photos", + postalCode: "postalCode", + region: "region", + streetAddress: "streetAddress", + title: "title", + urls: "urls" + }; + + module.exports = fieldType; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactFindOptions.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactFindOptions.js new file mode 100644 index 0000000..c662363 --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactFindOptions.js @@ -0,0 +1,37 @@ +cordova.define("cordova-plugin-contacts.ContactFindOptions", function(require, exports, module) { /* + * + * 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. + * +*/ + +/** + * ContactFindOptions. + * @constructor + * @param filter used to match contacts against + * @param multiple boolean used to determine if more than one contact should be returned + */ + +var ContactFindOptions = function(filter, multiple, desiredFields) { + this.filter = filter || ''; + this.multiple = (typeof multiple != 'undefined' ? multiple : false); + this.desiredFields = typeof desiredFields != 'undefined' ? desiredFields : []; +}; + +module.exports = ContactFindOptions; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactName.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactName.js new file mode 100644 index 0000000..41b5e6d --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactName.js @@ -0,0 +1,43 @@ +cordova.define("cordova-plugin-contacts.ContactName", function(require, exports, module) { /* + * + * 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. + * +*/ + +/** +* Contact name. +* @constructor +* @param formatted // NOTE: not part of W3C standard +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +module.exports = ContactName; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactOrganization.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactOrganization.js new file mode 100644 index 0000000..8e1302e --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/ContactOrganization.js @@ -0,0 +1,43 @@ +cordova.define("cordova-plugin-contacts.ContactOrganization", function(require, exports, module) { /* + * + * 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. + * +*/ + +/** +* Contact organization. +* @constructor +* @param pref +* @param type +* @param name +* @param dept +* @param title +*/ + +var ContactOrganization = function(pref, type, name, dept, title) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +module.exports = ContactOrganization; + +}); diff --git a/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/contacts.js b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/contacts.js new file mode 100644 index 0000000..d761b9b --- /dev/null +++ b/app/src/main/assets/www/boilerplate/cordova/plugins/cordova-plugin-contacts/www/contacts.js @@ -0,0 +1,100 @@ +cordova.define("cordova-plugin-contacts.contacts", function(require, exports, module) { /* + * + * 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. + * +*/ + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + ContactError = require('./ContactError'), + utils = require('cordova/utils'), + Contact = require('./Contact'), + fieldType = require('./ContactFieldType'); + + +/** +* Represents a group of Contacts. +* @constructor +*/ +var contacts = { + fieldType: fieldType, + /** + * Returns an array of Contacts matching the search criteria. + * @param fields that should be searched + * @param successCB success callback + * @param errorCB error callback + * @param {ContactFindOptions} options that can be applied to contact searching + * @return array of Contacts matching search criteria + */ + find:function(fields, successCB, errorCB, options) { + argscheck.checkArgs('afFO', 'contacts.find', arguments); + if (!fields.length) { + errorCB && errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); + } else { + // missing 'options' param means return all contacts + options = options || {filter: '', multiple: true} + var win = function(result) { + var cs = []; + for (var i = 0, l = result.length; i < l; i++) { + cs.push(contacts.create(result[i])); + } + successCB(cs); + }; + exec(win, errorCB, "Contacts", "search", [fields, options]); + } + }, + + /** + * This function picks contact from phone using contact picker UI + * @returns new Contact object + */ + pickContact: function (successCB, errorCB) { + + argscheck.checkArgs('fF', 'contacts.pick', arguments); + + var win = function (result) { + // if Contacts.pickContact return instance of Contact object + // don't create new Contact object, use current + var contact = result instanceof Contact ? result : contacts.create(result); + successCB(contact); + }; + exec(win, errorCB, "Contacts", "pickContact", []); + }, + + /** + * This function creates a new contact, but it does not persist the contact + * to device storage. To persist the contact to device storage, invoke + * contact.save(). + * @param properties an object whose properties will be examined to create a new Contact + * @returns new Contact object + */ + create:function(properties) { + argscheck.checkArgs('O', 'contacts.create', arguments); + var contact = new Contact(); + for (var i in properties) { + if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { + contact[i] = properties[i]; + } + } + return contact; + } +}; + +module.exports = contacts; + +}); diff --git a/app/src/main/java/org/apache/cordova/contacts/ContactAccessor.java b/app/src/main/java/org/apache/cordova/contacts/ContactAccessor.java new file mode 100644 index 0000000..eb3cbbf --- /dev/null +++ b/app/src/main/java/org/apache/cordova/contacts/ContactAccessor.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed 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.util.HashMap; + +import android.util.Log; +import org.apache.cordova.CordovaInterface; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * This abstract class defines SDK-independent API for communication with + * Contacts Provider. The actual implementation used by the application depends + * on the level of API available on the device. If the API level is Cupcake or + * Donut, we want to use the {@link ContactAccessorSdk3_4} class. If it is + * Eclair or higher, we want to use {@link ContactAccessorSdk5}. + */ +public abstract class ContactAccessor { + + protected final String LOG_TAG = "ContactsAccessor"; + protected CordovaInterface mApp; + + /** + * Check to see if the data associated with the key is required to + * be populated in the Contact object. + * @param key + * @param map created by running buildPopulationSet. + * @return true if the key data is required + */ + protected boolean isRequired(String key, HashMap map) { + Boolean retVal = map.get(key); + return (retVal == null) ? false : retVal.booleanValue(); + } + + /** + * Create a hash map of what data needs to be populated in the Contact object + * @param fields the list of fields to populate + * @return the hash map of required data + */ + protected HashMap buildPopulationSet(JSONObject options) { + HashMap map = new HashMap(); + + String key; + try { + JSONArray desiredFields = null; + if (options!=null && options.has("desiredFields")) { + desiredFields = options.getJSONArray("desiredFields"); + } + if (desiredFields == null || desiredFields.length() == 0) { + map.put("displayName", true); + map.put("name", true); + map.put("nickname", true); + map.put("phoneNumbers", true); + map.put("emails", true); + map.put("addresses", true); + map.put("ims", true); + map.put("organizations", true); + map.put("birthday", true); + map.put("note", true); + map.put("urls", true); + map.put("photos", true); + map.put("categories", true); + } else { + for (int i = 0; i < desiredFields.length(); i++) { + key = desiredFields.getString(i); + if (key.startsWith("displayName")) { + map.put("displayName", true); + } else if (key.startsWith("name")) { + map.put("displayName", true); + map.put("name", true); + } else if (key.startsWith("nickname")) { + map.put("nickname", true); + } else if (key.startsWith("phoneNumbers")) { + map.put("phoneNumbers", true); + } else if (key.startsWith("emails")) { + map.put("emails", true); + } else if (key.startsWith("addresses")) { + map.put("addresses", true); + } else if (key.startsWith("ims")) { + map.put("ims", true); + } else if (key.startsWith("organizations")) { + map.put("organizations", true); + } else if (key.startsWith("birthday")) { + map.put("birthday", true); + } else if (key.startsWith("note")) { + map.put("note", true); + } else if (key.startsWith("urls")) { + map.put("urls", true); + } else if (key.startsWith("photos")) { + map.put("photos", true); + } else if (key.startsWith("categories")) { + map.put("categories", true); + } + } + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return map; + } + + /** + * Convenience method to get a string from a JSON object. Saves a + * lot of try/catch writing. + * If the property is not found in the object null will be returned. + * + * @param obj contact object to search + * @param property to be looked up + * @return The value of the property + */ + protected String getJsonString(JSONObject obj, String property) { + String value = null; + try { + if (obj != null) { + value = obj.getString(property); + if (value.equals("null")) { + Log.d(LOG_TAG, property + " is string called 'null'"); + value = null; + } + } + } + catch (JSONException e) { + Log.d(LOG_TAG, "Could not get = " + e.getMessage()); + } + return value; + } + + /** + * Handles adding a JSON Contact object into the database. + * @return TODO + */ + public abstract String save(JSONObject contact); + + /** + * Handles searching through SDK-specific contacts API. + */ + public abstract JSONArray search(JSONArray filter, JSONObject options); + + /** + * Handles searching through SDK-specific contacts API. + * @throws JSONException + */ + public abstract JSONObject getContactById(String id) throws JSONException; + + /** + * Handles searching through SDK-specific contacts API. + * @param desiredFields fields that will filled. All fields will be filled if null + * @throws JSONException + */ + public abstract JSONObject getContactById(String id, JSONArray desiredFields) throws JSONException; + + /** + * Handles removing a contact from the database. + */ + public abstract boolean remove(String id); + + /** + * A class that represents the where clause to be used in the database query + */ + class WhereOptions { + private String where; + private String[] whereArgs; + public void setWhere(String where) { + this.where = where; + } + public String getWhere() { + return where; + } + public void setWhereArgs(String[] whereArgs) { + this.whereArgs = whereArgs; + } + public String[] getWhereArgs() { + return whereArgs; + } + } +} diff --git a/app/src/main/java/org/apache/cordova/contacts/ContactAccessorSdk5.java b/app/src/main/java/org/apache/cordova/contacts/ContactAccessorSdk5.java new file mode 100644 index 0000000..541e11d --- /dev/null +++ b/app/src/main/java/org/apache/cordova/contacts/ContactAccessorSdk5.java @@ -0,0 +1,2202 @@ +/* + 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.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +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.net.Uri; +import android.os.RemoteException; +import android.provider.ContactsContract; +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: + *

    + *
  • It sees contacts from multiple accounts. + *
  • It works with aggregated contacts. So for example, if the contact is the result + * of aggregation of two raw contacts from different accounts, it may return the name from + * one and the phone number from the other. + *
  • It is efficient because it uses the more efficient current API. + *
  • Not obvious in this particular example, but it has access to new kinds + * of data available exclusively through the new APIs. Exercise for the reader: add support + * for nickname (see {@link android.provider.ContactsContract.CommonDataKinds.Nickname}) or + * social status updates (see {@link android.provider.ContactsContract.StatusUpdates}). + *
+ */ + +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 = ".+@.+\\.+.+"; /* @.*/ + + /** + * 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", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.formatted", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.familyName", ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME); + dbMap.put("name.givenName", ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME); + dbMap.put("name.middleName", ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME); + dbMap.put("name.honorificPrefix", ContactsContract.CommonDataKinds.StructuredName.PREFIX); + dbMap.put("name.honorificSuffix", ContactsContract.CommonDataKinds.StructuredName.SUFFIX); + dbMap.put("nickname", ContactsContract.CommonDataKinds.Nickname.NAME); + dbMap.put("phoneNumbers", ContactsContract.CommonDataKinds.Phone.NUMBER); + dbMap.put("phoneNumbers.value", ContactsContract.CommonDataKinds.Phone.NUMBER); + dbMap.put("emails", ContactsContract.CommonDataKinds.Email.DATA); + dbMap.put("emails.value", ContactsContract.CommonDataKinds.Email.DATA); + dbMap.put("addresses", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.formatted", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.streetAddress", ContactsContract.CommonDataKinds.StructuredPostal.STREET); + dbMap.put("addresses.locality", ContactsContract.CommonDataKinds.StructuredPostal.CITY); + dbMap.put("addresses.region", ContactsContract.CommonDataKinds.StructuredPostal.REGION); + dbMap.put("addresses.postalCode", ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE); + dbMap.put("addresses.country", ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY); + dbMap.put("ims", ContactsContract.CommonDataKinds.Im.DATA); + dbMap.put("ims.value", ContactsContract.CommonDataKinds.Im.DATA); + dbMap.put("organizations", ContactsContract.CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.name", ContactsContract.CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.department", ContactsContract.CommonDataKinds.Organization.DEPARTMENT); + dbMap.put("organizations.title", ContactsContract.CommonDataKinds.Organization.TITLE); + dbMap.put("birthday", ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE); + dbMap.put("note", ContactsContract.CommonDataKinds.Note.NOTE); + dbMap.put("photos.value", ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE); + //dbMap.put("categories.value", null); + dbMap.put("urls", ContactsContract.CommonDataKinds.Website.URL); + dbMap.put("urls.value", ContactsContract.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; + + 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. + } + } + else { + searchTerm = "%"; + } + + + //Log.d(LOG_TAG, "Search Term = " + searchTerm); + //Log.d(LOG_TAG, "Field Length = " + fields.length()); + //Log.d(LOG_TAG, "Fields = " + fields.toString()); + + // 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); + + // 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); + + // 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(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + } + if (isRequired("name", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.PREFIX); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.SUFFIX); + } + if (isRequired("phoneNumbers", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Phone._ID); + columnsToFetch.add(ContactsContract.CommonDataKinds.Phone.NUMBER); + columnsToFetch.add(ContactsContract.CommonDataKinds.Phone.TYPE); + } + if (isRequired("emails", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Email._ID); + columnsToFetch.add(ContactsContract.CommonDataKinds.Email.DATA); + columnsToFetch.add(ContactsContract.CommonDataKinds.Email.TYPE); + } + if (isRequired("addresses", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal._ID); + columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TYPE); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.STREET); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.CITY); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.REGION); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE); + columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY); + } + if (isRequired("organizations", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Organization._ID); + columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TYPE); + columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.DEPARTMENT); + columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.COMPANY); + columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TITLE); + } + if (isRequired("ims", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Im._ID); + columnsToFetch.add(ContactsContract.CommonDataKinds.Im.DATA); + columnsToFetch.add(ContactsContract.CommonDataKinds.Im.TYPE); + } + if (isRequired("note", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Note.NOTE); + } + if (isRequired("nickname", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Nickname.NAME); + } + if (isRequired("urls", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Website._ID); + columnsToFetch.add(ContactsContract.CommonDataKinds.Website.URL); + columnsToFetch.add(ContactsContract.CommonDataKinds.Website.TYPE); + } + if (isRequired("birthday", populate)) { + columnsToFetch.add(ContactsContract.CommonDataKinds.Event.START_DATE); + columnsToFetch.add(ContactsContract.CommonDataKinds.Event.TYPE); + } + if (isRequired("photos", populate)) { + columnsToFetch.add(ContactsContract.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); + 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 (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(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + int colNote = c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE); + int colNickname = c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME); + int colBirthday = c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE); + int colEventType = c.getColumnIndex(ContactsContract.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(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) && isRequired("name", populate)) { + contact.put("displayName", c.getString(colDisplayName)); + } + + if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + && isRequired("name", populate)) { + contact.put("name", nameQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + && isRequired("phoneNumbers", populate)) { + phones.put(phoneQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + && isRequired("emails", populate)) { + emails.put(emailQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + && isRequired("addresses", populate)) { + addresses.put(addressQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + && isRequired("organizations", populate)) { + organizations.put(organizationQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) + && isRequired("ims", populate)) { + ims.put(imQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) + && isRequired("note", populate)) { + contact.put("note", c.getString(colNote)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + && isRequired("nickname", populate)) { + contact.put("nickname", c.getString(colNickname)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) + && isRequired("urls", populate)) { + websites.put(websiteQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)) { + if (isRequired("birthday", populate) && + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(colEventType)) { + contact.put("birthday", c.getString(colBirthday)); + } + } + else if (mimetype.equals(ContactsContract.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) { + 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("%")) { + 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) { + + 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)) { + 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(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("nickname") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("phoneNumbers") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("emails") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("addresses") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("ims") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("organizations") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("note") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE); + where.add("(" + dbMap.get("urls") + " LIKE ? AND " + + ContactsContract.Data.MIMETYPE + " = ? )"); + whereArgs.add(searchTerm); + whereArgs.add(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE); + } + } + + /* + * Special case for when the user wants all the contacts but + */ + if ("%".equals(searchTerm)) { + options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); + options.setWhereArgs(new String[] { searchTerm }); + return options; + } + + 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(ContactsContract.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(ContactsContract.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(ContactsContract.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(ContactsContract.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(ContactsContract.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(ContactsContract.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(ContactsContract.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(ContactsContract.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(ContactsContract.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 "); + } + } + 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(ContactsContract.CommonDataKinds.Organization._ID))); + organization.put("pref", false); // Android does not store pref attribute + organization.put("type", getOrgType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TYPE)))); + organization.put("department", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.DEPARTMENT))); + organization.put("name", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.COMPANY))); + organization.put("title", cursor.getString(cursor.getColumnIndex(ContactsContract.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(ContactsContract.CommonDataKinds.StructuredPostal._ID))); + address.put("pref", false); // Android does not store pref attribute + address.put("type", getAddressType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TYPE)))); + address.put("formatted", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS))); + address.put("streetAddress", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.STREET))); + address.put("locality", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.CITY))); + address.put("region", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.REGION))); + address.put("postalCode", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE))); + address.put("country", cursor.getString(cursor.getColumnIndex(ContactsContract.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(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)); + String givenName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)); + String middleName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)); + String honorificPrefix = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.PREFIX)); + String honorificSuffix = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.SUFFIX)); + + // Create the formatted name + StringBuffer formatted = new StringBuffer(""); + if (honorificPrefix != null) { + formatted.append(honorificPrefix + " "); + } + if (givenName != null) { + formatted.append(givenName + " "); + } + if (middleName != null) { + formatted.append(middleName + " "); + } + if (familyName != null) { + formatted.append(familyName); + } + if (honorificSuffix != null) { + formatted.append(" " + honorificSuffix); + } + + 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(ContactsContract.CommonDataKinds.Phone._ID))); + phoneNumber.put("pref", false); // Android does not store pref attribute + phoneNumber.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))); + phoneNumber.put("type", getPhoneType(cursor.getInt(cursor.getColumnIndex(ContactsContract.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(ContactsContract.CommonDataKinds.Email._ID))); + email.put("pref", false); // Android does not store pref attribute + email.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA))); + email.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.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(ContactsContract.CommonDataKinds.Im._ID))); + im.put("pref", false); // Android does not store pref attribute + im.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + im.put("type", getImType(cursor.getString(cursor.getColumnIndex(ContactsContract.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(ContactsContract.CommonDataKinds.Website._ID))); + website.put("pref", false); // Android does not store pref attribute + website.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Website.URL))); + website.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.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(); + try { + photo.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.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 + Cursor photoCursor = mApp.getActivity().getContentResolver().query(photoUri, new String[] {ContactsContract.Contacts.Photo.PHOTO}, null, null, null); + if (photoCursor == null) { + return null; + } else { + if (!photoCursor.moveToFirst()) { + photoCursor.close(); + return null; + } + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + 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. + int rawId = (Integer.valueOf(getJsonString(contact, "rawId"))).intValue(); + + // 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, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE }); + + if (displayName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName); + } + + String familyName = getJsonString(name, "familyName"); + if (familyName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, familyName); + } + String middleName = getJsonString(name, "middleName"); + if (middleName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, middleName); + } + String givenName = getJsonString(name, "givenName"); + if (givenName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, givenName); + } + String honorificPrefix = getJsonString(name, "honorificPrefix"); + if (honorificPrefix != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, honorificPrefix); + } + String honorificSuffix = getJsonString(name, "honorificSuffix"); + if (honorificSuffix != null) { + builder.withValue(ContactsContract.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, ContactsContract.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, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")); + contentValues.put(ContactsContract.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(ContactsContract.CommonDataKinds.Phone._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { phoneId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")) + .withValue(ContactsContract.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, ContactsContract.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, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value")); + contentValues.put(ContactsContract.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(ContactsContract.CommonDataKinds.Email._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { emailId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value")) + .withValue(ContactsContract.CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type"))) + .build()); + } else { + ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.Email._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { emailId, ContactsContract.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, ContactsContract.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, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")); + contentValues.put(ContactsContract.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(ContactsContract.CommonDataKinds.StructuredPostal._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { addressId, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")) + .withValue(ContactsContract.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, ContactsContract.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, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))); + contentValues.put(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")); + contentValues.put(ContactsContract.CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")); + contentValues.put(ContactsContract.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(ContactsContract.CommonDataKinds.Organization._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { orgId, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))) + .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")) + .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")) + .withValue(ContactsContract.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, ContactsContract.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, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")); + contentValues.put(ContactsContract.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(ContactsContract.CommonDataKinds.Im._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { imId, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.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, ContactsContract.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, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Website.DATA, getJsonString(website, "value")); + contentValues.put(ContactsContract.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(ContactsContract.CommonDataKinds.Website._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { websiteId, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Website.DATA, getJsonString(website, "value")) + .withValue(ContactsContract.CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))) + .build()); + } + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get websites"); + } + + // Modify birthday + String birthday = getJsonString(contact, "birthday"); + if (birthday != null) { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=? AND " + + ContactsContract.CommonDataKinds.Event.TYPE + "=?", + new String[] { id, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, new String("" + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) }) + .withValue(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) + .withValue(ContactsContract.CommonDataKinds.Event.START_DATE, birthday) + .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, ContactsContract.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, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1); + contentValues.put(ContactsContract.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(ContactsContract.CommonDataKinds.Photo._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { photoId, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1) + .withValue(ContactsContract.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 id; + } 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, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Website.DATA, getJsonString(website, "value")) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))) + .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")) + .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value")) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.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 + * @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("http:") || path.startsWith("https:") || path.startsWith("file:")) { + URL url = new URL(path); + return url.openStream(); + } + else { + 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 + try { + JSONObject name = contact.optJSONObject("name"); + String displayName = contact.getString("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, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName) + .withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, getJsonString(name, "familyName")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, getJsonString(name, "middleName")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, getJsonString(name, "givenName")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, getJsonString(name, "honorificPrefix")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, getJsonString(name, "honorificSuffix")) + .build()); + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get name object"); + } + + //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, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.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, ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.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 + String birthday = getJsonString(contact, "birthday"); + if (birthday != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) + .withValue(ContactsContract.CommonDataKinds.Event.START_DATE, birthday) + .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"); + } + + cursor.close(); + + return (result > 0) ? true : false; + } + + /************************************************************************** + * + * 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 = ContactsContract.CommonDataKinds.Phone.TYPE_OTHER; + if (string != null) { + if ("home".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_HOME; + } + else if ("mobile".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE; + } + else if ("work".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_WORK; + } + else if ("work fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK; + } + else if ("home fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME; + } + else if ("fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK; + } + else if ("pager".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_PAGER; + } + else if ("other".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_OTHER; + } + else if ("car".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_CAR; + } + else if ("company main".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN; + } + else if ("isdn".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_ISDN; + } + else if ("main".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_MAIN; + } + else if ("other fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX; + } + else if ("radio".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_RADIO; + } + else if ("telex".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_TELEX; + } + else if ("work mobile".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE; + } + else if ("work pager".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER; + } + else if ("assistant".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT; + } + else if ("mms".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_MMS; + } + else if ("callback".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK; + } + else if ("tty ttd".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD; + } + else if ("custom".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.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 ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM: + stringType = "custom"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME: + stringType = "home fax"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK: + stringType = "work fax"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_HOME: + stringType = "home"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: + stringType = "mobile"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_PAGER: + stringType = "pager"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK: + stringType = "callback"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_CAR: + stringType = "car"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN: + stringType = "company main"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX: + stringType = "other fax"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_RADIO: + stringType = "radio"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_TELEX: + stringType = "telex"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD: + stringType = "tty tdd"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE: + stringType = "work mobile"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER: + stringType = "work pager"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT: + stringType = "assistant"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_MMS: + stringType = "mms"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_ISDN: + stringType = "isdn"; + break; + case ContactsContract.CommonDataKinds.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 = ContactsContract.CommonDataKinds.Email.TYPE_OTHER; + if (string != null) { + if ("home".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Email.TYPE_HOME; + } + else if ("work".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Email.TYPE_WORK; + } + else if ("other".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Email.TYPE_OTHER; + } + else if ("mobile".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Email.TYPE_MOBILE; + } + else if ("custom".equals(string.toLowerCase())) { + return ContactsContract.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 ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM: + stringType = "custom"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_HOME: + stringType = "home"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_MOBILE: + stringType = "mobile"; + break; + case ContactsContract.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 = ContactsContract.CommonDataKinds.Organization.TYPE_OTHER; + if (string != null) { + if ("work".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Organization.TYPE_WORK; + } + else if ("other".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Organization.TYPE_OTHER; + } + else if ("custom".equals(string.toLowerCase())) { + return ContactsContract.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 ContactsContract.CommonDataKinds.Organization.TYPE_CUSTOM: + stringType = "custom"; + break; + case ContactsContract.CommonDataKinds.Organization.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.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 = ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER; + if (string != null) { + if ("work".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK; + } + else if ("other".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER; + } + else if ("home".equals(string.toLowerCase())) { + return ContactsContract.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 ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME: + stringType = "home"; + break; + case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.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 = ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM; + if (string != null) { + if ("aim".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM; + } + else if ("google talk".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK; + } + else if ("icq".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ; + } + else if ("jabber".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER; + } + else if ("msn".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN; + } + else if ("netmeeting".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING; + } + else if ("qq".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ; + } + else if ("skype".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE; + } + else if ("yahoo".equals(string.toLowerCase())) { + return ContactsContract.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 ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM: + stringType = "AIM"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK: + stringType = "Google Talk"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ: + stringType = "ICQ"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER: + stringType = "Jabber"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN: + stringType = "MSN"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING: + stringType = "NetMeeting"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ: + stringType = "QQ"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE: + stringType = "Skype"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO: + stringType = "Yahoo"; + break; + default: + stringType = "custom"; + break; + } + return stringType; + } + +} diff --git a/app/src/main/java/org/apache/cordova/contacts/ContactInfoDTO.java b/app/src/main/java/org/apache/cordova/contacts/ContactInfoDTO.java new file mode 100644 index 0000000..a61b6e7 --- /dev/null +++ b/app/src/main/java/org/apache/cordova/contacts/ContactInfoDTO.java @@ -0,0 +1,59 @@ +/* + 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.util.HashMap; + +import org.json.JSONArray; +import org.json.JSONObject; + +public class ContactInfoDTO { + + String displayName; + JSONObject name; + JSONArray organizations; + JSONArray addresses; + JSONArray phones; + JSONArray emails; + JSONArray ims; + JSONArray websites; + JSONArray photos; + String note; + String nickname; + String birthday; + HashMap desiredFieldsWithVals; + + public ContactInfoDTO() { + + displayName = ""; + name = new JSONObject(); + organizations = new JSONArray(); + addresses = new JSONArray(); + phones = new JSONArray(); + emails = new JSONArray(); + ims = new JSONArray(); + websites = new JSONArray(); + photos = new JSONArray(); + note = ""; + nickname = ""; + desiredFieldsWithVals = new HashMap(); + } + +} diff --git a/app/src/main/java/org/apache/cordova/contacts/ContactManager.java b/app/src/main/java/org/apache/cordova/contacts/ContactManager.java new file mode 100644 index 0000000..1bb9c43 --- /dev/null +++ b/app/src/main/java/org/apache/cordova/contacts/ContactManager.java @@ -0,0 +1,191 @@ +/* + 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 org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.RawContacts; +import android.util.Log; + +public class ContactManager extends CordovaPlugin { + + private ContactAccessor contactAccessor; + private CallbackContext callbackContext; // The callback context from which we were invoked. + private JSONArray executeArgs; + + private static final String LOG_TAG = "Contact Query"; + + public static final int UNKNOWN_ERROR = 0; + public static final int INVALID_ARGUMENT_ERROR = 1; + public static final int TIMEOUT_ERROR = 2; + public static final int PENDING_OPERATION_ERROR = 3; + public static final int IO_ERROR = 4; + public static final int NOT_SUPPORTED_ERROR = 5; + public static final int PERMISSION_DENIED_ERROR = 20; + private static final int CONTACT_PICKER_RESULT = 1000; + + /** + * Constructor. + */ + public ContactManager() { + } + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArray of arguments for the plugin. + * @param callbackContext The callback context used when calling back into JavaScript. + * @return True if the action was valid, false otherwise. + */ + public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { + + this.callbackContext = callbackContext; + this.executeArgs = args; + + /** + * Check to see if we are on an Android 1.X device. If we are return an error as we + * do not support this as of Cordova 1.0. + */ + if (android.os.Build.VERSION.RELEASE.startsWith("1.")) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR)); + return true; + } + + /** + * Only create the contactAccessor after we check the Android version or the program will crash + * older phones. + */ + if (this.contactAccessor == null) { + this.contactAccessor = new ContactAccessorSdk5(this.cordova); + } + + if (action.equals("search")) { + final JSONArray filter = args.getJSONArray(0); + final JSONObject options = args.get(1) == null ? null : args.getJSONObject(1); + this.cordova.getThreadPool().execute(new Runnable() { + public void run() { + JSONArray res = contactAccessor.search(filter, options); + callbackContext.success(res); + } + }); + } + else if (action.equals("save")) { + final JSONObject contact = args.getJSONObject(0); + this.cordova.getThreadPool().execute(new Runnable(){ + public void run() { + JSONObject res = null; + String id = contactAccessor.save(contact); + if (id != null) { + try { + res = contactAccessor.getContactById(id); + } catch (JSONException e) { + Log.e(LOG_TAG, "JSON fail.", e); + } + } + if (res != null) { + callbackContext.success(res); + } else { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR)); + } + } + }); + } + else if (action.equals("remove")) { + final String contactId = args.getString(0); + this.cordova.getThreadPool().execute(new Runnable() { + public void run() { + if (contactAccessor.remove(contactId)) { + callbackContext.success(); + } else { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR)); + } + } + }); + } + else if (action.equals("pickContact")) { + pickContactAsync(); + } + else { + return false; + } + return true; + } + + /** + * Launches the Contact Picker to select a single contact. + */ + private void pickContactAsync() { + final CordovaPlugin plugin = (CordovaPlugin) this; + Runnable worker = new Runnable() { + public void run() { + Intent contactPickerIntent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); + plugin.cordova.startActivityForResult(plugin, contactPickerIntent, CONTACT_PICKER_RESULT); + } + }; + this.cordova.getThreadPool().execute(worker); + } + + /** + * Called when user picks contact. + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @throws JSONException + */ + public void onActivityResult(int requestCode, int resultCode, final Intent intent) { + if (requestCode == CONTACT_PICKER_RESULT) { + if (resultCode == Activity.RESULT_OK) { + String contactId = intent.getData().getLastPathSegment(); + // to populate contact data we require Raw Contact ID + // so we do look up for contact raw id first + Cursor c = this.cordova.getActivity().getContentResolver().query(RawContacts.CONTENT_URI, + new String[] {RawContacts._ID}, RawContacts.CONTACT_ID + " = " + contactId, null, null); + if (!c.moveToFirst()) { + this.callbackContext.error("Error occured while retrieving contact raw id"); + return; + } + String id = c.getString(c.getColumnIndex(RawContacts._ID)); + c.close(); + + try { + JSONObject contact = contactAccessor.getContactById(id); + this.callbackContext.success(contact); + return; + } catch (JSONException e) { + Log.e(LOG_TAG, "JSON fail.", e); + } + } else if (resultCode == Activity.RESULT_CANCELED){ + this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.NO_RESULT, UNKNOWN_ERROR)); + return; + } + this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR)); + } + } +} diff --git a/app/src/main/res/xml/config.xml b/app/src/main/res/xml/config.xml index 90684da..fb84105 100644 --- a/app/src/main/res/xml/config.xml +++ b/app/src/main/res/xml/config.xml @@ -65,6 +65,10 @@ + + + +