/**
* The MIT License
* -------------------------------------------------------------
* Copyright (c) 2008, Rob Ellis, Brock Whitten, Brian Leroux, Joe Bowser, Dave Johnson, Nitobi
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.phonegap.api.impl;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.microedition.pim.Contact;
import javax.microedition.pim.PIM;
import javax.microedition.pim.PIMException;
import net.rim.blackberry.api.pdap.BlackBerryContact;
import net.rim.blackberry.api.pdap.BlackBerryContactList;
import com.phonegap.PhoneGap;
import com.phonegap.api.Command;
/**
* Finds data in agenda.
*
* @author Jose Noheda
* @author Fil Maj
*
*/
public class ContactsCommand implements Command {
private static final int SEARCH_COMMAND = 0;
private static final int GET_ALL_COMMAND = 1;
private static final int CHOOSE_COMMAND = 2;
private static final int REMOVE_COMMAND = 3;
private static final int NEW_COMMAND = 4;
private static final String CODE = "PhoneGap=contacts";
private static final String CONTACT_MANAGER_JS_NAMESPACE = "navigator.contacts";
private static final String ERROR_NO_CONTACTID = ";alert('[PhoneGap Error] Contact ID not specified during contact removal operation.');";
/**
* Determines whether the specified instruction is accepted by the command.
* @param instruction The string instruction passed from JavaScript via cookie.
* @return true if the Command accepts the instruction, false otherwise.
*/
public boolean accept(String instruction) {
return instruction != null && instruction.startsWith(CODE);
}
/**
* Invokes internal phone application.
*/
public String execute(String instruction) {
Hashtable options = ContactsCommand.parseParameters(instruction);
switch (getCommand(instruction)) {
case SEARCH_COMMAND:
return getAgenda(options);
case GET_ALL_COMMAND:
return getAgenda(options);
case CHOOSE_COMMAND:
return chooseContact();
case REMOVE_COMMAND:
return removeContact(options);
case NEW_COMMAND:
return newContact(options);
}
return null;
}
/**
* Parses the options object and returns a hash of params.
* @param instruction The cookie/string representation of the instruction.
* @return Hashtable Hash of key:value pairs containing the parameter names & values.
*/
private static Hashtable parseParameters(String instruction) {
String[] params = PhoneGap.splitString(instruction, '/', false);
int numParams = params.length;
Hashtable hash = new Hashtable();
for (int i = 0; i < numParams; i++) {
String curParam = params[i];
if (curParam.indexOf(':') == -1) continue;
String[] key_value = PhoneGap.splitString(curParam, ':', false);
if (key_value.length < 2) continue;
String key = key_value[0];
String value = key_value[1];
hash.put(key, value);
curParam = null;
key_value = null;
key = null;
value = null;
}
params = null;
return hash;
}
private int getCommand(String instruction) {
String command = instruction.substring(instruction.indexOf('/') + 1);
if (command.startsWith("search")) return SEARCH_COMMAND;
if (command.startsWith("getall")) return GET_ALL_COMMAND;
if (command.startsWith("choose")) return CHOOSE_COMMAND;
if (command.startsWith("remove")) return REMOVE_COMMAND;
if (command.startsWith("new")) return NEW_COMMAND;
return -1;
}
/**
* Creates a new contact based on the hash of parameters passed in via options.
* @param options Parsed parameters for use with creating a new contact.
* @return String, which will be executed back in browser. Just callback invokes.
*/
private String newContact(Hashtable options) {
boolean winfail = false;
BlackBerryContactList agenda = null;
BlackBerryContact contact = null;
String[] nameField = new String[2];
String numbers = null;
String emails = null;
try {
agenda = (BlackBerryContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);
contact = (BlackBerryContact) agenda.createContact();
// Add name(s).
nameField[Contact.NAME_FAMILY] = options.get("lastName").toString();
nameField[Contact.NAME_GIVEN] = options.get("firstName").toString();
if (agenda.isSupportedField(Contact.NAME)) contact.addStringArray(Contact.NAME, Contact.ATTR_NONE, nameField);
// TODO: Need to finalize JSON representation of address - it's multi-field in BlackBerry :s.
// TODO: Figure out how attributes and fields work for contact in BlackBerry. RUN TESTS! Code below may change.
numbers = options.get("phoneNumber").toString();
if (agenda.isSupportedField(Contact.TEL)) contact.addString(Contact.TEL, Contact.ATTR_MOBILE, numbers.substring(numbers.lastIndexOf('=')+1));
emails = options.get("email").toString();
if (agenda.isSupportedField(Contact.EMAIL)) contact.addString(Contact.EMAIL, Contact.ATTR_MOBILE, emails.substring(emails.lastIndexOf('=')+1));
contact.commit();
winfail = true;
} catch (PIMException e) {
winfail = false;
} finally {
agenda = null;
contact = null;
nameField = null;
numbers = null;
emails = null;
}
if (winfail) {
return ";if (" + CONTACT_MANAGER_JS_NAMESPACE + ".new_onSuccess) { " + CONTACT_MANAGER_JS_NAMESPACE + ".new_onSuccess(); };";
} else {
return ";if (" + CONTACT_MANAGER_JS_NAMESPACE + ".new_onError) { " + CONTACT_MANAGER_JS_NAMESPACE + ".new_onError(); };";
}
}
/**
* Removes the specified contact from the contact list.
* @param options A hash of options (parameters) passed by the PhoneGap app. Needs to contain a 'contactID' property for the removal to go through properly.
* @return JavaScript that will be evaluated by the PhoneGap app - only callbacks.
*/
private String removeContact(Hashtable options) {
if (options.contains("contactID")) {
try {
BlackBerryContactList agenda = (BlackBerryContactList) PIM
.getInstance().openPIMList(PIM.CONTACT_LIST,
PIM.READ_WRITE);
Contact matchContact = agenda.createContact();
int contactID = Integer.parseInt(options.get("contactID").toString());
if (agenda.isSupportedField(Contact.UID)) matchContact.addInt(Contact.UID, Contact.ATTR_HOME | Contact.ATTR_PREFERRED, contactID);
Enumeration matches = agenda.items(matchContact);
// TODO: Finish this implementation.
if (matches.hasMoreElements()) {
// Matched to a contact.
} else {
// No matches found - call error callback.
}
} catch (Exception e) {
e.printStackTrace();
// Trigger error callback if exception occurs.
return ";if (" + CONTACT_MANAGER_JS_NAMESPACE + ".remove_onError) { " + CONTACT_MANAGER_JS_NAMESPACE + ".remove_onError(); };";
}
} else {
return ERROR_NO_CONTACTID;
}
return null;
}
/**
* Invokes the default BlackBerry contact chooser to allow the user to choose a contact.
* @return JSON representation of the chosen contact, which will then be sent back to JavaScript.
*/
private String chooseContact() {
boolean winfail = false;
StringBuffer contacts = new StringBuffer("[");
BlackBerryContact blackberryContact = null;
BlackBerryContactList agenda = null;
try {
agenda = (BlackBerryContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_ONLY);
if (agenda != null) {
blackberryContact = (BlackBerryContact) agenda.choose();
agenda.close();
ContactsCommand.addContactToBuffer(contacts, blackberryContact);
contacts.append("];");
winfail = true;
} else {
// TODO: If cannot get reference to Agenda, should the error or success callback be called?
winfail = false;
}
} catch (Exception e) {
// TODO: No error callbacks associated with contact chooser - what to do?
winfail = false;
} finally {
blackberryContact = null;
agenda = null;
}
if (winfail) {
return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.toString() + "if (" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess) { " + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess();" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess = null; };";
} else {
return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.append("];").toString() + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess = null;";
}
}
/**
* Returns a contact list, either all contacts or contacts matching the optional search parameter.
* @param options A hash of options to pass into retrieving contacts. These can include name filters and paging parameters.
* @return JSON string representing the contacts that are retrieved, plus necessary JavaScript callbacks.
*/
private String getAgenda(Hashtable options) {
String callbackHook = "";
try {
BlackBerryContactList agenda = (BlackBerryContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_ONLY);
StringBuffer contacts = new StringBuffer("[");
if (agenda != null) {
Enumeration matches;
String name = options.get("nameFilter")!=null?options.get("nameFilter").toString():"";
if (name != "") {
matches = agenda.itemsByName(name);
callbackHook = "search_";
} else {
matches = agenda.items();
callbackHook = "global_";
}
int pageSize = 0, pageNumber = 0;
if (options.contains("pageSize")) pageSize = Integer.parseInt(options.get("pageSize").toString());
if (options.contains("pageNumber")) pageNumber = Integer.parseInt(options.get("pageNumber").toString());
if (pageSize > 0) {
for (int i = 0; i < pageSize*pageNumber && matches.hasMoreElements(); i++) {
matches.nextElement();
}
for (int j = 0; j < pageSize && matches.hasMoreElements(); j++) {
BlackBerryContact contact = (BlackBerryContact)matches.nextElement();
ContactsCommand.addContactToBuffer(contacts, contact);
contacts.append(',');
contact = null;
}
} else {
while (matches.hasMoreElements()) {
BlackBerryContact contact = (BlackBerryContact)matches.nextElement();
ContactsCommand.addContactToBuffer(contacts, contact);
contacts.append(',');
contact = null;
}
}
if (contacts.length() > 1) contacts = contacts.deleteCharAt(contacts.length() - 1);
contacts.append("];");
// Return an assignment to the contact manager contacts array with the contacts JSON generated above.
// Also call the right onSuccess if it exists.
return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.toString() + "if (" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess) { " + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess();" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess = null;" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError = null; };";
} else {
// TODO: If cannot get reference to Agenda, should the error or success callback be called?
return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.append("];").toString() + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess = null;" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError = null;";
}
} catch (Exception ex) {
System.out.println("Exception getting contact list: " + ex.getMessage());
return ";if (" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError) { " + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError();" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess = null;" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError = null; };";
}
}
private static void addContactToBuffer(StringBuffer buff, BlackBerryContact contact) {
// TODO: Eventually extend this to return proper labels/values for differing phone/email types.
buff.append("{");
if (contact.countValues(Contact.EMAIL) > 0) {
buff.append("emails:[{'types':['mobile'],'address':'");
buff.append(contact.getString(Contact.EMAIL, 0));
buff.append("'}]");
}
final int numValues = contact.countValues(Contact.TEL);
if (numValues > 0) {
boolean sentinel = false;
String phoneMobile = "";
for (int index = 0; index < numValues; index++)
{
final int curAttributes = contact.getAttributes(Contact.TEL, index);
// TODO: For now, we are only looking for the mobile contact number.
if ((curAttributes & Contact.ATTR_MOBILE) == Contact.ATTR_MOBILE)
{
phoneMobile = contact.getString(Contact.TEL, index);
sentinel = true;
break;
}
}
if (sentinel) {
if (buff.length() > 1) buff.append(",");
buff.append("phoneNumber:[{'types':['mobile'],'number':'");
buff.append(phoneMobile);
buff.append("'}]");
}
}
// See if there is a meaningful name set for the contact.
if (contact.countValues(Contact.NAME) > 0) {
if (buff.length() > 1) buff.append(",");
buff.append("name:{");
buff.append("givenName:'");
final String[] name = contact.getStringArray(Contact.NAME, 0);
final String firstName = name[Contact.NAME_GIVEN];
final String lastName = name[Contact.NAME_FAMILY];
buff.append((firstName != null ? firstName : "") + "',familyName:'");
buff.append((lastName != null ? lastName : "") + "'");
String formatted = (firstName != null ? firstName : "");
formatted += (lastName != null? " " + lastName : "");
buff.append(",'formatted':'" + formatted + "'}");
}
buff.append("}");
}
}