You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by pu...@apache.org on 2014/05/28 00:47:17 UTC

[2/5] git commit: Add pickContact functionality to cordova contacts plugin

Add pickContact functionality to cordova contacts plugin


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/commit/d656191c
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/tree/d656191c
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/diff/d656191c

Branch: refs/heads/master
Commit: d656191c4072cbef0bf5b3b5f5eb4dfe4817d25b
Parents: 6ed8b90
Author: Vladimir Kotikov <vl...@akvelon.com>
Authored: Wed Apr 2 10:38:12 2014 +0400
Committer: Vladimir Kotikov <vl...@akvelon.com>
Committed: Wed Apr 2 10:58:01 2014 +0400

----------------------------------------------------------------------
 doc/index.md                         |  56 ++++--
 plugin.xml                           |  23 ++-
 src/android/ContactAccessor.java     |  67 ++++---
 src/android/ContactAccessorSdk5.java |  81 ++++----
 src/android/ContactInfoDTO.java      |  43 +++++
 src/android/ContactManager.java      |  62 +++++-
 src/ios/CDVContacts.h                |  11 ++
 src/ios/CDVContacts.m                |  36 +++-
 src/windows8/ContactProxy.js         | 173 +++++++----------
 src/wp/ContactPicker.xaml            |  58 ++++++
 src/wp/ContactPicker.xaml.cs         | 110 +++++++++++
 src/wp/ContactPickerTask.cs          | 107 +++++++++++
 src/wp/Contacts.cs                   | 308 ++++++++++--------------------
 src/wp/ContactsHelper.cs             | 277 +++++++++++++++++++++++++++
 www/ContactFieldType.js              |  38 ++++
 www/ContactFindOptions.js            |   4 +-
 www/contacts.js                      |  28 ++-
 17 files changed, 1077 insertions(+), 405 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/doc/index.md
----------------------------------------------------------------------
diff --git a/doc/index.md b/doc/index.md
index 2f41254..b13966b 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -58,12 +58,18 @@ __WARNING__: All privileged apps enforce [Content Security Policy](https://devel
 		}
 	}
 
+### Windows 8 Quirks
+
+Windows 8 Contacts are readonly. Via the Cordova API Contacts are not queryable/searchable, you should inform the user to pick a contact as a call to contacts.pickContact which will open the 'People' app where the user must choose a contact.
+Any contacts returned are readonly, so your application cannot modify them.
+
 ## navigator.contacts
 
 ### Methods
 
 - navigator.contacts.create
 - navigator.contacts.find
+- navigator.contacts.pickContact
 
 ### Objects
 
@@ -74,6 +80,7 @@ __WARNING__: All privileged apps enforce [Content Security Policy](https://devel
 - ContactOrganization
 - ContactFindOptions
 - ContactError
+- ContactFieldType
 
 ## navigator.contacts.create
 
@@ -89,9 +96,6 @@ database, for which you need to invoke the `Contact.save` method.
 - Firefox OS
 - iOS
 - Windows Phone 7 and 8
-- Windows 8 ( Note: Windows 8 Contacts are readonly via the Cordova API
-Contacts are not queryable/searchable, you should inform the user to pick a contact as a call to contacts.find will open the 'People' app where the user must choose a contact.
-Any contacts returned are readonly, so your application cannot modify them. )
 
 ### Example
 
@@ -105,9 +109,7 @@ The resulting objects are passed to the `contactSuccess` callback
 function specified by the __contactSuccess__ parameter.
 
 The __contactFields__ parameter specifies the fields to be used as a
-search qualifier, and only those results are passed to the
-__contactSuccess__ callback function.  A zero-length __contactFields__
-parameter is invalid and results in
+search qualifier.  A zero-length __contactFields__ parameter is invalid and results in
 `ContactError.INVALID_ARGUMENT_ERROR`. A __contactFields__ value of
 `"*"` returns all contact fields.
 
@@ -115,22 +117,25 @@ The __contactFindOptions.filter__ string can be used as a search
 filter when querying the contacts database.  If provided, a
 case-insensitive, partial value match is applied to each field
 specified in the __contactFields__ parameter.  If there's a match for
-_any_ of the specified fields, the contact is returned.
+_any_ of the specified fields, the contact is returned. Use __contactFindOptions.desiredFields__
+parameter to control which contact properties must be returned back.
 
 ### Parameters
 
-- __contactFields__: Contact fields to use as a search qualifier. The resulting `Contact` object only features values for these fields. _(DOMString[])_ [Required]
-
 - __contactSuccess__: Success callback function invoked with the array of Contact objects returned from the database. [Required]
 
 - __contactError__: Error callback function, invoked when an error occurs. [Optional]
 
+- __contactFields__: Contact fields to use as a search qualifier. _(DOMString[])_ [Required]
+
 - __contactFindOptions__: Search options to filter navigator.contacts. [Optional] Keys include:
 
 - __filter__: The search string used to find navigator.contacts. _(DOMString)_ (Default: `""`)
 
 - __multiple__: Determines if the find operation returns multiple navigator.contacts. _(Boolean)_ (Default: `false`)
 
+    - __desiredFields__: Contact fields to be returned back. If specified, the resulting `Contact` object only features values for these fields. _(DOMString[])_ [Optional]
+
 ### Supported Platforms
 
 - Android
@@ -138,7 +143,6 @@ _any_ of the specified fields, the contact is returned.
 - Firefox OS
 - iOS
 - Windows Phone 7 and 8
-- Windows 8 ( read-only support, search requires user interaction, contactFields are ignored, only contactFindOptions.multiple is used to enable the user to select 1 or many contacts. )
 
 ### Example
 
@@ -154,9 +158,36 @@ _any_ of the specified fields, the contact is returned.
     var options      = new ContactFindOptions();
     options.filter   = "Bob";
     options.multiple = true;
-    var fields       = ["displayName", "name"];
-    navigator.contacts.find(fields, onSuccess, onError, options);
+    options.desiredFields = [navigator.contacts.fieldType.id];
+    var fields       = [navigator.contacts.fieldType.displayName, navigator.contacts.fieldType.name];
+    navigator.contacts.find(onSuccess, onError, fields, options);
 
+## navigator.contacts.pickContact
+
+The `navigator.contacts.pickContact` method launches the Contact Picker to select a single contact.
+The resulting object is passed to the `contactSuccess` callback
+function specified by the __contactSuccess__ parameter.
+
+### Parameters
+
+- __contactSuccess__: Success callback function invoked with the single Contact object. [Required]
+
+- __contactError__: Error callback function, invoked when an error occurs. [Optional]
+
+### Supported Platforms
+
+- Android
+- iOS
+- Windows Phone 8
+- Windows 8
+
+### Example
+
+    navigator.contacts.pickContact(function(contact){
+            console.log('The following contact has been selected:' + JSON.stringify(contact));
+        },function(err){
+            console.log('Error: ' + err);
+        });
 
 ## Contact
 
@@ -216,6 +247,7 @@ for details.
 - Firefox OS
 - iOS
 - Windows Phone 7 and 8
+- Windows 8
 
 ### Save Example
 

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/plugin.xml
----------------------------------------------------------------------
diff --git a/plugin.xml b/plugin.xml
index acfc080..bdaec08 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -45,8 +45,9 @@
         <clobbers target="ContactOrganization" />
     </js-module>
 
-
-
+    <js-module src="www/ContactFieldType.js" name="ContactFieldType">
+            <merge target="" />
+    </js-module>
 
     <!-- android -->
     <platform name="android">
@@ -65,6 +66,7 @@
         <source-file src="src/android/ContactAccessor.java" target-dir="src/org/apache/cordova/contacts" />
         <source-file src="src/android/ContactAccessorSdk5.java" target-dir="src/org/apache/cordova/contacts" />
         <source-file src="src/android/ContactManager.java" target-dir="src/org/apache/cordova/contacts" />
+        <source-file src="src/android/ContactInfoDTO.java" target-dir="src/org/apache/cordova/contacts" />
     </platform>
 
     <!-- amazon-fireos -->
@@ -160,6 +162,7 @@
         </config-file>
 
         <source-file src="src/wp/Contacts.cs" />
+        <source-file src="src/wp/ContactsHelper.cs" />
     </platform>
 
     <!-- wp8 -->
@@ -175,6 +178,10 @@
         </config-file>
 
         <source-file src="src/wp/Contacts.cs" />
+        <source-file src="src/wp/ContactsHelper.cs" />
+        <source-file src="src/wp/ContactPicker.xaml" />
+        <source-file src="src/wp/ContactPicker.xaml.cs" />
+        <source-file src="src/wp/ContactPickerTask.cs" />
     </platform>
     
     <!-- firefoxos -->
@@ -187,11 +194,13 @@
         <js-module src="src/firefoxos/ContactsProxy.js" name="ContactsProxy">
             <runs />
         </js-module>
-    </platform>  
+    </platform>    
 
-        <!-- wp8 -->
-    <js-module src="src/windows8/ContactProxy.js" name="ContactProxy">
-        <merges target="" />
-    </js-module>
+    <!-- Windows 8 -->
+    <platform name="windows8">
+        <js-module src="src/windows8/ContactProxy.js" name="ContactProxy">
+            <merges target="" />
+        </js-module>
+    </platform>
 
 </plugin>

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/android/ContactAccessor.java
----------------------------------------------------------------------
diff --git a/src/android/ContactAccessor.java b/src/android/ContactAccessor.java
index bac243e..879fe68 100644
--- a/src/android/ContactAccessor.java
+++ b/src/android/ContactAccessor.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (c) Microsoft Open Technologies, Inc.  
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,7 +20,6 @@ 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;
@@ -54,12 +54,16 @@ public abstract class ContactAccessor {
      * @param fields the list of fields to populate
      * @return the hash map of required data
      */
-    protected HashMap<String,Boolean> buildPopulationSet(JSONArray fields) {
-        HashMap<String,Boolean> map = new HashMap<String,Boolean>();
+    protected HashMap<String, Boolean> buildPopulationSet(JSONObject options) {
+        HashMap<String, Boolean> map = new HashMap<String, Boolean>();
 
         String key;
         try {
-            if (fields.length() == 1 && fields.getString(0).equals("*")) {
+            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);
@@ -73,54 +77,40 @@ public abstract class ContactAccessor {
                 map.put("urls", true);
                 map.put("photos", true);
                 map.put("categories", true);
-           } 
-            else {
-                for (int i=0; i<fields.length(); i++) {
-                    key = fields.getString(i);
+            } 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")) {
+                    } else if (key.startsWith("name")) {
                         map.put("displayName", true);
                         map.put("name", true);
-                    }
-                    else if (key.startsWith("nickname")) {
+                    } else if (key.startsWith("nickname")) {
                         map.put("nickname", true);
-                    }
-                    else if (key.startsWith("phoneNumbers")) {
+                    } else if (key.startsWith("phoneNumbers")) {
                         map.put("phoneNumbers", true);
-                    }
-                    else if (key.startsWith("emails")) {
+                    } else if (key.startsWith("emails")) {
                         map.put("emails", true);
-                    }
-                    else if (key.startsWith("addresses")) {
+                    } else if (key.startsWith("addresses")) {
                         map.put("addresses", true);
-                    }
-                    else if (key.startsWith("ims")) {
+                    } else if (key.startsWith("ims")) {
                         map.put("ims", true);
-                    }
-                    else if (key.startsWith("organizations")) {
+                    } else if (key.startsWith("organizations")) {
                         map.put("organizations", true);
-                    }
-                    else if (key.startsWith("birthday")) {
+                    } else if (key.startsWith("birthday")) {
                         map.put("birthday", true);
-                    }
-                    else if (key.startsWith("note")) {
+                    } else if (key.startsWith("note")) {
                         map.put("note", true);
-                    }
-                    else if (key.startsWith("urls")) {
+                    } else if (key.startsWith("urls")) {
                         map.put("urls", true);
-                    }
-                    else if (key.startsWith("photos")) {
+                    } else if (key.startsWith("photos")) {
                         map.put("photos", true);
-                    }
-                    else if (key.startsWith("categories")) {
+                    } else if (key.startsWith("categories")) {
                         map.put("categories", true);
                     }
                 }
             }
-       }
-        catch (JSONException e) {
+        } catch (JSONException e) {
             Log.e(LOG_TAG, e.getMessage(), e);
         }
         return map;
@@ -168,12 +158,19 @@ public abstract class ContactAccessor {
      * @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 
      */

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/android/ContactAccessorSdk5.java
----------------------------------------------------------------------
diff --git a/src/android/ContactAccessorSdk5.java b/src/android/ContactAccessorSdk5.java
index d8b9592..9178b38 100644
--- a/src/android/ContactAccessorSdk5.java
+++ b/src/android/ContactAccessorSdk5.java
@@ -15,28 +15,12 @@
        KIND, either express or implied.  See the License for the
        specific language governing permissions and limitations
        under the License.
-*/
 
+       Copyright (c) Microsoft Open Technologies, Inc.
+*/
+ 
 package org.apache.cordova.contacts;
 
-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;
-
-import org.apache.cordova.CordovaInterface;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -49,8 +33,24 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
-//import android.app.Activity;
-//import android.content.Context;
+
+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.
@@ -84,6 +84,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
      * A static map that converts the JavaScript property name to Android database column name.
      */
     private static final Map<String, String> dbMap = new HashMap<String, String>();
+
     static {
         dbMap.put("id", ContactsContract.Data.CONTACT_ID);
         dbMap.put("displayName", ContactsContract.Contacts.DISPLAY_NAME);
@@ -126,7 +127,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
     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.
@@ -169,7 +170,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
         //Log.d(LOG_TAG, "Fields = " + fields.toString());
 
         // Loop through the fields the user provided to see what data should be returned.
-        HashMap<String, Boolean> populate = buildPopulationSet(fields);
+        HashMap<String, Boolean> populate = buildPopulationSet(options);
 
         // Build the ugly where clause and where arguments for one big query.
         WhereOptions whereOptions = buildWhereClause(fields, searchTerm);
@@ -268,7 +269,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
                 idOptions.getWhere(),
                 idOptions.getWhereArgs(),
                 ContactsContract.Data.CONTACT_ID + " ASC");
-
+         
         JSONArray contacts = populateContactArray(limit, populate, c);
         return contacts;
     }
@@ -281,17 +282,23 @@ public class ContactAccessorSdk5 extends ContactAccessor {
      * @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,
+        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");
 
-        JSONArray fields = new JSONArray();
-        fields.put("*");
-
-        HashMap<String, Boolean> populate = buildPopulationSet(fields);
+        HashMap<String, Boolean> populate = buildPopulationSet(
+                new JSONObject().put("desiredFields", desiredFields)
+                );
 
         JSONArray contacts = populateContactArray(1, populate, c);
 
@@ -383,7 +390,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
                     // 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)) {
+                    if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) && isRequired("name", populate)) {
                         contact.put("displayName", c.getString(colDisplayName));
                     }
 
@@ -894,7 +901,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
             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, (new Long(contactId)));
+            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());
         } catch (JSONException e) {
@@ -968,7 +975,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
     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 = (new Integer(getJsonString(contact, "rawId"))).intValue();
+        int rawId = (Integer.valueOf(getJsonString(contact, "rawId"))).intValue();
 
         // Create a list of attributes to add to the contact database
         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
@@ -1098,8 +1105,8 @@ public class ContactAccessorSdk5 extends ContactAccessor {
                         }
                         // This is an existing email so do a DB update
                         else {
-                        	String emailValue=getJsonString(email, "value");
-                        	if(!emailValue.isEmpty()) {
+                         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 + "=?",
@@ -1107,13 +1114,13 @@ public class ContactAccessorSdk5 extends ContactAccessor {
                                     .withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value"))
                                     .withValue(ContactsContract.CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type")))
                                     .build());
-                        	} else {
+                         } 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());
-                        	}
+                         }
                         }
                     }
                 }
@@ -2177,5 +2184,5 @@ public class ContactAccessorSdk5 extends ContactAccessor {
         }
         return stringType;
     }
-}
 
+}

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/android/ContactInfoDTO.java
----------------------------------------------------------------------
diff --git a/src/android/ContactInfoDTO.java b/src/android/ContactInfoDTO.java
new file mode 100644
index 0000000..52a066f
--- /dev/null
+++ b/src/android/ContactInfoDTO.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Microsoft Open Technologies, Inc. Licensed under the Apache License, Version 2.0 (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<String, Object> 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<String, Object>();
+ }
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/android/ContactManager.java
----------------------------------------------------------------------
diff --git a/src/android/ContactManager.java b/src/android/ContactManager.java
index a50d799..7e88893 100644
--- a/src/android/ContactManager.java
+++ b/src/android/ContactManager.java
@@ -15,6 +15,8 @@
        KIND, either express or implied.  See the License for the
        specific language governing permissions and limitations
        under the License.
+
+       Copyright (c) Microsoft Open Technologies, Inc.
 */
 package org.apache.cordova.contacts;
 
@@ -24,11 +26,18 @@ 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.provider.ContactsContract.Contacts;
 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;
@@ -38,6 +47,7 @@ public class ContactManager extends CordovaPlugin {
     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.
@@ -54,6 +64,10 @@ public class ContactManager extends CordovaPlugin {
      * @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.
@@ -73,7 +87,7 @@ public class ContactManager extends CordovaPlugin {
 
         if (action.equals("search")) {
             final JSONArray filter = args.getJSONArray(0);
-            final JSONObject options = args.getJSONObject(1);
+            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);
@@ -83,7 +97,7 @@ public class ContactManager extends CordovaPlugin {
         }
         else if (action.equals("save")) {
             final JSONObject contact = args.getJSONObject(0);
-            this.cordova.getThreadPool().execute(new Runnable() {
+            this.cordova.getThreadPool().execute(new Runnable(){
                 public void run() {
                     JSONObject res = null;
                     String id = contactAccessor.save(contact);
@@ -114,9 +128,53 @@ public class ContactManager extends CordovaPlugin {
                 }
             });
         }
+        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();
+                try {
+                    JSONObject contact = contactAccessor.getContactById(contactId);
+                    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));
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/ios/CDVContacts.h
----------------------------------------------------------------------
diff --git a/src/ios/CDVContacts.h b/src/ios/CDVContacts.h
index e3deb21..294c48e 100644
--- a/src/ios/CDVContacts.h
+++ b/src/ios/CDVContacts.h
@@ -15,6 +15,8 @@
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.
+ 
+ Copyright (c) Microsoft Open Technologies, Inc.
  */
 
 #import <Foundation/Foundation.h>
@@ -64,6 +66,15 @@
 - (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person;
 - (BOOL)personViewController:(ABPersonViewController*)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person
                     property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue;
+/*
+ * Launches the Contact Picker to select a single contact.
+ *
+ * arguments:
+ *	1: this is the javascript function that will be called with the contact data as a JSON object (as the first param)
+ * options:
+ *	desiredFields: ContactFields array to be returned back
+ */
+- (void)pickContact:(CDVInvokedUrlCommand*)command;
 
 /*
  * search - searches for contacts.  Only person records are currently supported.

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/ios/CDVContacts.m
----------------------------------------------------------------------
diff --git a/src/ios/CDVContacts.m b/src/ios/CDVContacts.m
index aa0c9c7..b8513bf 100644
--- a/src/ios/CDVContacts.m
+++ b/src/ios/CDVContacts.m
@@ -15,6 +15,8 @@
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.
+ 
+ Copyright (c) Microsoft Open Technologies, Inc.
  */
 
 #import "CDVContacts.h"
@@ -211,6 +213,29 @@
     }
 }
 
+- (void)pickContact:(CDVInvokedUrlCommand *)command
+{
+    // mimic chooseContact method call with required for us parameters
+    NSArray* desiredFields = [command.arguments objectAtIndex:0 withDefault:[NSNull null]];
+    if (desiredFields == nil || desiredFields.count == 0) {
+        desiredFields = [NSArray arrayWithObjects:@"*", nil];
+    }
+    NSMutableDictionary* options = [NSMutableDictionary dictionaryWithCapacity:2];
+    
+    [options setObject: desiredFields forKey:@"fields"];
+    [options setObject: [NSNumber numberWithBool: FALSE] forKey:@"allowsEditing"];
+    
+    NSArray* args = [NSArray arrayWithObjects:options, nil];
+    
+    CDVInvokedUrlCommand* newCommand = [[CDVInvokedUrlCommand alloc] initWithArguments:args
+                 callbackId:command.callbackId
+                  className:command.className
+                 methodName:command.methodName];
+    
+    [self chooseContact:newCommand];
+    
+}
+
 - (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker
       shouldContinueAfterSelectingPerson:(ABRecordRef)person
 {
@@ -313,6 +338,7 @@
             // get the findOptions values
             BOOL multiple = NO;         // default is false
             NSString* filter = nil;
+            NSArray* desiredFields = nil;
             if (![findOptions isKindOfClass:[NSNull class]]) {
                 id value = nil;
                 filter = (NSString*)[findOptions objectForKey:@"filter"];
@@ -322,9 +348,15 @@
                     multiple = [(NSNumber*)value boolValue];
                     // NSLog(@"multiple is: %d", multiple);
                 }
+                desiredFields = [findOptions objectForKey:@"desiredFields"];
+                // return all fields if desired fields are not explicitly defined
+                if (desiredFields == nil || desiredFields.count == 0) {
+                    desiredFields = [NSArray arrayWithObjects:@"*", nil];
+                }
             }
 
-            NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields];
+            NSDictionary* searchFields = [[CDVContact class] calcReturnFields:fields];
+            NSDictionary* returnFields = [[CDVContact class] calcReturnFields:desiredFields];
 
             NSMutableArray* matches = nil;
             if (!filter || [filter isEqualToString:@""]) {
@@ -351,7 +383,7 @@
                 for (int j = 0; j < testCount; j++) {
                     CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:j]];
                     if (testContact) {
-                        bFound = [testContact foundValue:filter inFields:returnFields];
+                        bFound = [testContact foundValue:filter inFields:searchFields];
                         if (bFound) {
                             [matches addObject:testContact];
                         }

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/windows8/ContactProxy.js
----------------------------------------------------------------------
diff --git a/src/windows8/ContactProxy.js b/src/windows8/ContactProxy.js
index 10f9402..960ae5c 100644
--- a/src/windows8/ContactProxy.js
+++ b/src/windows8/ContactProxy.js
@@ -18,121 +18,88 @@
  * specific language governing permissions and limitations
  * under the License.
  *
+ * Copyright (c) Microsoft Open Technologies, Inc.
 */
 
+var ContactField = require('./ContactField'),
+    ContactAddress = require('./ContactAddress'),
+    ContactName = require('./ContactName'),
+    Contact = require('./Contact');
 
-module.exports = {
-    search:function(win,fail,args){
-        var fields = args[0]; // ignored, always returns entire object
-        var options = args[1];
 
-        var filter = options.filter;   // ignored
-        var multiple = true;//options.multiple;
+function convertToContact(windowsContact) {
+    var contact = new Contact();
+
+    // displayName & nickname
+    contact.displayName = windowsContact.name;
+    contact.nickname = windowsContact.name;
+
+    // name
+    contact.name = new ContactName(windowsContact.name);
+
+    // phoneNumbers
+    contact.phoneNumbers = [];
+    for (var i = 0; i < windowsContact.phoneNumbers.size; i++) {
+        var phone = new ContactField(windowsContact.phoneNumbers[i].category, windowsContact.phoneNumbers[i].value);
+        contact.phoneNumbers.push(phone);
+    }
+
+    // emails
+    contact.emails = [];
+    for (var i = 0; i < windowsContact.emails.size; i++) {
+        var email = new ContactField(windowsContact.emails[i].category, windowsContact.emails[i].value);
+        contact.emails.push(email);
+    }
+
+    // addressres
+    contact.addresses = [];
+    for (var i = 0; i < windowsContact.locations.size; i++) {
+        var address = new ContactAddress(null, windowsContact.locations[i].category,
+            windowsContact.locations[i].unstructuredAddress, windowsContact.locations[i].street,
+            null, windowsContact.locations[i].region, windowsContact.locations[i].postalCode,
+            windowsContact.locations[i].country);
+        contact.addresses.push(address);
+    }
+
+    // ims
+    contact.ims = [];
+    for (var i = 0; i < windowsContact.instantMessages.size; i++) {
+        var im = new ContactField(windowsContact.instantMessages[i].category, windowsContact.instantMessages[i].userName);
+        contact.ims.push(im);
+    }
+
+    return contact;
+};
+
+module.exports = {
+    pickContact: function(win, fail, args) {
 
         var picker = new Windows.ApplicationModel.Contacts.ContactPicker();
-        picker.selectionMode = Windows.ApplicationModel.Contacts.ContactSelectionMode.contacts;   // select entire contact
+        picker.selectionMode = Windows.ApplicationModel.Contacts.ContactSelectionMode.contacts; // select entire contact
+
+        // pickContactAsync is available on Windows 8.1 or later, instead of
+        // pickSingleContactAsync, which is deprecated after Windows 8,
+        // so try to use newer method, if available.
+        // see http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.contacts.contactpicker.picksinglecontactasync.aspx
         if (picker.pickContactAsync) {
             // TODO: 8.1 has better contact support via the 'Contact' object
-        }
-        else {
-            // 8.0 use the ContactInformation class
-            // decide which function we will call
-            var pickerFunkName = multiple ? 'pickMultipleContactsAsync' : 'pickSingleContactAsync';
-            picker[pickerFunkName]().done(function (res) {
-                if (!res) {
-                    fail && setTimeout(function () {
+        } else {
+
+            function success(con) {
+                // if contact was not picked
+                if (!con) {
+                    fail && setTimeout(function() {
                         fail(new Error("User did not pick a contact."));
                     }, 0);
                     return;
                 }
 
-                var contactResults = [];
-
-                for (var i = 0; i < res.length; i++) {
-
-
-                    var index,
-                        contactResult = res[i],
-                        contact = {
-                            id: "",
-                            name: { formatted: contactResult.name },  // ContactName
-                            displayName: contactResult.name,          // DOMString
-                            nickname: contactResult.name,             // DOMString
-                            phoneNumbers: contactResult.phoneNumbers, // ContactField[]
-                            addresses: contactResult.locations,       // ContactAddress[]
-                            emails: [],                               // ContactField
-                            ims: contactResult.instantMessages,       // ContactField[]
-                            organizations: [],              // ContactOrganization[]
-                            birthday: null,                 // Date
-                            note: "",                       // DOMString
-                            photos: [],                     // ContactField[]
-                            categories: [],                 // ContactField[]
-                            urls: []                        // ContactField[]
-                        };
-
-                    // Win8-ContactField is {category, name, type, value};
-                    // Cordova ContactField is {type,value, pref:bool };
-                    // Win8 type means 'email' cordova type means 'work|home|...' so we convert them
-                    if (contact.emails && contact.emails.length) {
-                        contact.emails[0].pref = true; // add a preferred prop 
-                        for (index = 0; index < contacts.emails.length; index++) {
-                            contact.emails[index].type = contact.emails[index].category;
-                        }
-                    }
-
-                    if (contact.phoneNumbers && contact.phoneNumbers.length) {
-                        contact.phoneNumbers[0].pref = true; // cordova contact field needs a 'prefered' property on  a contact
-                        // change the meaning of type from 'telephonenumber' to 'work|home|...'
-                        for (index = 0; index < contact.phoneNumbers.length; index++) {
-                            contact.phoneNumbers[index].type = contact.phoneNumbers[index].category;
-                        }
-                    }
-
-                    if (contact.addresses && contact.addresses.length) {
-
-                        // convert addresses/locations to Cordova.ContactAddresses                    
-                        // constr: ContactAddress(pref, type, formatted, streetAddress, locality, region, postalCode, country)
-                        var address, formatted;
-                        for (index = 0; index < contact.addresses.length; index++) {
-                            address = contact.addresses[index];   // make an alias
-                            var formattedArray = [];
-                            // get rid of the empty fields.
-                            var fields = [address.street, address.city, address.region, address.country, address.postalCode];
-                            for (var n = 0; n < fields.length; n++) {
-                                if (fields[n].length > 0) {
-                                    formattedArray.push(fields[n]);
-                                }
-                            }
-                            formattedAddress = formattedArray.join(", ");
-                            console.log(contact.name.formatted + " formatted looks like " + formattedAddress);
-                            contact.addresses[index] = new ContactAddress(false,
-                                                                          address.name,
-                                                                          formattedAddress,
-                                                                          address.street,
-                                                                          address.city,
-                                                                          address.region,
-                                                                          address.postalCode,
-                                                                          address.country);
-                        }
-
-                    }
-
-                    // convert ims to ContactField
-                    if (contact.ims && contact.ims.length) {
-                        // MS ContactInstantMessageField has : displayText, launchUri, service, userName, category, type
-                        contact.ims[0].pref = true;
-                        for (index = 0; index < contact.ims.length; index++) {
-                            contact.ims[index] = new ContactField(contact.ims[index].type, contact.ims[index].value, false);
-                        }
-                    }
-
-                    contactResults.push(contact);
-
-                }
                 // send em back
-                win(contactResults);
+                win(convertToContact(con));
+
+            }
 
-            });
+            picker.pickSingleContactAsync().done(success, fail);
         }
     },
 
@@ -141,7 +108,13 @@ module.exports = {
         fail && setTimeout(function () {
             fail(new Error("Contact create/save not supported on Windows 8"));
         }, 0);
+    },
 
+    search: function(win, fail, args) {
+        console && console.error && console.error("Error : Windows 8 does not support searching contacts");
+        fail && setTimeout(function() {
+            fail(new Error("Contact search not supported on Windows 8"));
+        }, 0);
     }
 
 

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/wp/ContactPicker.xaml
----------------------------------------------------------------------
diff --git a/src/wp/ContactPicker.xaml b/src/wp/ContactPicker.xaml
new file mode 100644
index 0000000..4f95823
--- /dev/null
+++ b/src/wp/ContactPicker.xaml
@@ -0,0 +1,58 @@
+<!--
+  Copyright (c) Microsoft Open Technologies, Inc. Licensed under the Apache License, Version 2.0 (the "License").
+-->
+<phone:PhoneApplicationPage
+    x:Class="WPCordovaClassLib.Cordova.Commands.ContactPicker"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
+    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
+    FontFamily="{StaticResource PhoneFontFamilyNormal}"
+    FontSize="{StaticResource PhoneFontSizeLarge}"
+    Foreground="{StaticResource PhoneForegroundBrush}"
+    SupportedOrientations="Portrait" Orientation="Portrait"
+    shell:SystemTray.IsVisible="False">
+
+    <Grid x:Name="ContentRoot" Background="Transparent">
+        
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"></RowDefinition>
+            <RowDefinition Height="*"></RowDefinition>
+        </Grid.RowDefinitions>
+
+        <TextBlock Name="HeaderBlock"
+                   Text="CHOOSE A CONTACT" 
+                   Margin="20,50,0,50" 
+                   FontSize="{StaticResource PhoneFontSizeMedium}"
+                   FontFamily="{StaticResource PhoneFontFamilySemiBold}"
+                   Grid.Row="0"
+                   />
+
+        <TextBlock Name="NoContactsBlock"
+                   Margin="20,0,0,0" 
+                   Text="Sorry, we couldn't find any contacts."
+                   TextWrapping="Wrap"
+                   FontSize="{StaticResource PhoneFontSizeLarge}"
+                   FontFamily="{StaticResource PhoneFontFamilyLight}"
+                   Visibility="Collapsed"
+                   Grid.Row="1"
+                   />
+        
+        <phone:LongListSelector Name="lstContacts"
+                                SelectionChanged="ContactsListSelectionChanged"
+                                Grid.Row="1">
+            <phone:LongListSelector.ItemTemplate>
+                <DataTemplate>
+                    <StackPanel Orientation="Horizontal" Margin="16,0,0,0">
+                        <StackPanel Margin="0,20,0,10">
+                            <TextBlock Text="{Binding DisplayName}"
+                                       TextWrapping="Wrap"
+                                       Style="{StaticResource PhoneTextExtraLargeStyle}" />
+                        </StackPanel>
+                    </StackPanel>
+                </DataTemplate>
+            </phone:LongListSelector.ItemTemplate>
+        </phone:LongListSelector>
+        
+    </Grid>
+</phone:PhoneApplicationPage>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/wp/ContactPicker.xaml.cs
----------------------------------------------------------------------
diff --git a/src/wp/ContactPicker.xaml.cs b/src/wp/ContactPicker.xaml.cs
new file mode 100644
index 0000000..90484fd
--- /dev/null
+++ b/src/wp/ContactPicker.xaml.cs
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Microsoft Open Technologies, Inc. Licensed under the Apache License, Version 2.0 (the "License").
+ */
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+    using System;
+    using System.Linq;
+    using System.Windows;
+    using System.Windows.Controls;
+    using System.Windows.Navigation;
+    using Microsoft.Phone.Tasks;
+    using Microsoft.Phone.UserData;
+    using DeviceContacts = Microsoft.Phone.UserData.Contacts;
+
+    /// <summary>
+    /// Custom implemented class for picking single contact
+    /// </summary>
+    public partial class ContactPicker
+    {
+        #region Fields
+
+        /// <summary>
+        /// Result of ContactPicker call, represent contact returned.
+        /// </summary>
+        private ContactPickerTask.PickResult result;
+
+        #endregion
+
+        #region Constructors
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ContactPicker"/> class. 
+        /// </summary>
+        public ContactPicker()
+        {
+            InitializeComponent();
+            var cons = new DeviceContacts();
+            cons.SearchCompleted += this.OnSearchCompleted;
+            cons.SearchAsync(string.Empty, FilterKind.None, string.Empty);
+        }
+
+        #endregion
+
+        #region Callbacks
+
+        /// <summary>
+        /// Occurs when contact is selected or pick operation cancelled.
+        /// </summary>
+        public event EventHandler<ContactPickerTask.PickResult> Completed;
+
+        #endregion
+
+        /// <summary>
+        /// The on navigated from.
+        /// </summary>
+        /// <param name="e">
+        /// The e.
+        /// </param>
+        protected override void OnNavigatedFrom(NavigationEventArgs e)
+        {
+            if (this.result == null)
+            {
+                this.Completed(this, new ContactPickerTask.PickResult(TaskResult.Cancel));
+            }
+
+            base.OnNavigatedFrom(e);
+        }
+
+        /// <summary>
+        /// Called when contacts retrieval completed.
+        /// </summary>
+        /// <param name="sender">The sender.</param>
+        /// <param name="e">The <see cref="ContactsSearchEventArgs"/> instance containing the event data.</param>
+        private void OnSearchCompleted(object sender, ContactsSearchEventArgs e)
+        {
+            if (e.Results.Count() != 0)
+            {
+                lstContacts.ItemsSource = e.Results.ToList();
+                lstContacts.Visibility = Visibility.Visible;
+                NoContactsBlock.Visibility = Visibility.Collapsed;
+            }
+            else
+            {
+                lstContacts.Visibility = Visibility.Collapsed;
+                NoContactsBlock.Visibility = Visibility.Visible;
+            }
+        }
+
+        /// <summary>
+        /// Called when any contact is selected.
+        /// </summary>
+        /// <param name="sender">
+        /// The sender.
+        /// </param>
+        /// <param name="e">
+        /// The e.
+        /// </param>
+        private void ContactsListSelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            this.result = new ContactPickerTask.PickResult(TaskResult.OK) { Contact = e.AddedItems[0] as Contact };
+            this.Completed(this, this.result);
+
+            if (NavigationService.CanGoBack)
+            {
+                NavigationService.GoBack();
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/wp/ContactPickerTask.cs
----------------------------------------------------------------------
diff --git a/src/wp/ContactPickerTask.cs b/src/wp/ContactPickerTask.cs
new file mode 100644
index 0000000..1228cc0
--- /dev/null
+++ b/src/wp/ContactPickerTask.cs
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) Microsoft Open Technologies, Inc. Licensed under the Apache License, Version 2.0 (the "License").
+ */
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+    using System;
+    using System.Windows;
+    using Microsoft.Phone.Controls;
+    using Microsoft.Phone.Tasks;
+    using Microsoft.Phone.UserData;
+
+    /// <summary>
+    /// Allows an application to pick contact. 
+    /// Use this to allow users to pick contact from your application.
+    /// </summary>
+    public class ContactPickerTask
+    {
+        /// <summary>
+        /// Occurs when a Pick task is completed.
+        /// </summary>
+        public event EventHandler<PickResult> Completed;
+
+        /// <summary>
+        /// Shows Contact pick application
+        /// </summary>
+        public void Show()
+        {
+            Deployment.Current.Dispatcher.BeginInvoke(() =>
+            {
+                var root = Application.Current.RootVisual as PhoneApplicationFrame;
+
+                string baseUrl = "/";
+
+                if (root != null)
+                {
+                    root.Navigated += this.OnNavigate;
+
+                    // dummy parameter is used to always open a fresh version
+                    root.Navigate(
+                        new Uri(
+                            baseUrl + "Plugins/org.apache.cordova.contacts/ContactPicker.xaml?dummy="
+                            + Guid.NewGuid(),
+                            UriKind.Relative));
+                }
+            });
+        }
+
+        /// <summary>
+        /// Performs additional configuration of the picker application.
+        /// </summary>
+        /// <param name="sender">The source of the event.</param>
+        /// <param name="e">The <see cref="System.Windows.Navigation.NavigationEventArgs"/> instance containing the event data.</param>
+        private void OnNavigate(object sender, System.Windows.Navigation.NavigationEventArgs e)
+        {
+            if (!(e.Content is ContactPicker))
+            {
+                return;
+            }
+
+            var phoneApplicationFrame = Application.Current.RootVisual as PhoneApplicationFrame;
+            if (phoneApplicationFrame != null)
+            {
+                phoneApplicationFrame.Navigated -= this.OnNavigate;
+            }
+
+            ContactPicker contactPicker = (ContactPicker)e.Content;
+
+            if (contactPicker != null)
+            {
+                contactPicker.Completed += this.Completed;
+            }
+            else if (this.Completed != null)
+            {
+                this.Completed(this, new PickResult(TaskResult.Cancel));
+            }
+        }
+
+        /// <summary>
+        /// Represents contact returned
+        /// </summary>
+        public class PickResult : TaskEventArgs
+        {
+            /// <summary>
+            /// Initializes a new instance of the PickResult class.
+            /// </summary>
+            public PickResult()
+            {
+            }
+
+            /// <summary>
+            /// Initializes a new instance of the PickResult class
+            /// with the specified Microsoft.Phone.Tasks.TaskResult.
+            /// </summary>
+            /// <param name="taskResult">Associated Microsoft.Phone.Tasks.TaskResult</param>
+            public PickResult(TaskResult taskResult)
+                : base(taskResult)
+            {
+            }
+
+            /// <summary>
+            ///  Gets the contact.
+            /// </summary>
+            public Contact Contact { get; internal set; }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/d656191c/src/wp/Contacts.cs
----------------------------------------------------------------------
diff --git a/src/wp/Contacts.cs b/src/wp/Contacts.cs
index 5ae2144..0059c4e 100644
--- a/src/wp/Contacts.cs
+++ b/src/wp/Contacts.cs
@@ -10,12 +10,13 @@
 	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.
+
+    Copyright (c) Microsoft Open Technologies, Inc.
 */
 
 using Microsoft.Phone.Tasks;
 using Microsoft.Phone.UserData;
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Globalization;
@@ -32,8 +33,12 @@ namespace WPCordovaClassLib.Cordova.Commands
     {
         [DataMember]
         public string filter { get; set; }
+
         [DataMember]
         public bool multiple { get; set; }
+
+        [DataMember]
+        public string[] desiredFields { get; set; }
     }
 
     [DataContract]
@@ -41,6 +46,7 @@ namespace WPCordovaClassLib.Cordova.Commands
     {
         [DataMember]
         public string[] fields { get; set; }
+
         [DataMember]
         public SearchOptions options { get; set; }
     }
@@ -50,18 +56,25 @@ namespace WPCordovaClassLib.Cordova.Commands
     {
         [DataMember]
         public string formatted { get; set; }
+
         [DataMember]
         public string type { get; set; }
+
         [DataMember]
         public string streetAddress { get; set; }
+
         [DataMember]
         public string locality { get; set; }
+
         [DataMember]
         public string region { get; set; }
+
         [DataMember]
         public string postalCode { get; set; }
+
         [DataMember]
         public string country { get; set; }
+
         [DataMember]
         public bool pref { get; set; }
     }
@@ -71,14 +84,19 @@ namespace WPCordovaClassLib.Cordova.Commands
     {
         [DataMember]
         public string formatted { get; set; }
+
         [DataMember]
         public string familyName { get; set; }
+
         [DataMember]
         public string givenName { get; set; }
+
         [DataMember]
         public string middleName { get; set; }
+
         [DataMember]
         public string honorificPrefix { get; set; }
+
         [DataMember]
         public string honorificSuffix { get; set; }
     }
@@ -88,8 +106,10 @@ namespace WPCordovaClassLib.Cordova.Commands
     {
         [DataMember]
         public string type { get; set; }
+
         [DataMember]
         public string value { get; set; }
+
         [DataMember]
         public bool pref { get; set; }
     }
@@ -99,12 +119,16 @@ namespace WPCordovaClassLib.Cordova.Commands
     {
         [DataMember]
         public string type { get; set; }
+
         [DataMember]
         public string name { get; set; }
+
         [DataMember]
         public bool pref { get; set; }
+
         [DataMember]
         public string department { get; set; }
+
         [DataMember]
         public string title { get; set; }
     }
@@ -114,12 +138,16 @@ namespace WPCordovaClassLib.Cordova.Commands
     {
         [DataMember]
         public string id { get; set; }
+
         [DataMember]
         public string rawId { get; set; }
+
         [DataMember]
         public string displayName { get; set; }
+
         [DataMember]
         public string nickname { get; set; }
+
         [DataMember]
         public string note { get; set; }
 
@@ -154,7 +182,6 @@ namespace WPCordovaClassLib.Cordova.Commands
 
     public class Contacts : BaseCommand
     {
-
         public const int UNKNOWN_ERROR = 0;
         public const int INVALID_ARGUMENT_ERROR = 1;
         public const int TIMEOUT_ERROR = 2;
@@ -164,15 +191,9 @@ namespace WPCordovaClassLib.Cordova.Commands
         public const int PERMISSION_DENIED_ERROR = 20;
         public const int SYNTAX_ERR = 8;
 
-        public Contacts()
-        {
-
-        }
-
         // refer here for contact properties we can access: http://msdn.microsoft.com/en-us/library/microsoft.phone.tasks.savecontacttask_members%28v=VS.92%29.aspx
         public void save(string jsonContact)
         {
-
             // jsonContact is actually an array of 1 {contact}
             string[] args = JSON.JsonHelper.Deserialize<string[]>(jsonContact);
 
@@ -195,6 +216,7 @@ namespace WPCordovaClassLib.Cordova.Commands
             }
 
             #region contact.name
+
             if (contact.name != null)
             {
                 if (contact.name.givenName != null)
@@ -208,17 +230,21 @@ namespace WPCordovaClassLib.Cordova.Commands
                 if (contact.name.honorificPrefix != null)
                     contactTask.Title = contact.name.honorificPrefix;
             }
+
             #endregion
 
             #region contact.org
+
             if (contact.organizations != null && contact.organizations.Count() > 0)
             {
                 contactTask.Company = contact.organizations[0].name;
                 contactTask.JobTitle = contact.organizations[0].title;
             }
+
             #endregion
 
             #region contact.phoneNumbers
+
             if (contact.phoneNumbers != null && contact.phoneNumbers.Length > 0)
             {
                 foreach (JSONContactField field in contact.phoneNumbers)
@@ -238,15 +264,15 @@ namespace WPCordovaClassLib.Cordova.Commands
                     }
                 }
             }
+
             #endregion
 
             #region contact.emails
 
             if (contact.emails != null && contact.emails.Length > 0)
             {
-
                 // set up different email types if they are not explicitly defined
-                foreach (string type in new string[] { "personal", "work", "other" })
+                foreach (string type in new[] {"personal", "work", "other"})
                 {
                     foreach (JSONContactField field in contact.emails)
                     {
@@ -279,9 +305,9 @@ namespace WPCordovaClassLib.Cordova.Commands
                             contactTask.OtherEmail = field.value;
                         }
                     }
-
                 }
             }
+
             #endregion
 
             if (contact.note != null && contact.note.Length > 0)
@@ -290,6 +316,7 @@ namespace WPCordovaClassLib.Cordova.Commands
             }
 
             #region contact.addresses
+
             if (contact.addresses != null && contact.addresses.Length > 0)
             {
                 foreach (JSONContactAddress address in contact.addresses)
@@ -322,31 +349,33 @@ namespace WPCordovaClassLib.Cordova.Commands
                     }
                 }
             }
-            #endregion
 
+            #endregion
 
-            contactTask.Completed += new EventHandler<SaveContactResult>(ContactSaveTaskCompleted);
+            contactTask.Completed += ContactSaveTaskCompleted;
             contactTask.Show();
         }
 
-        void ContactSaveTaskCompleted(object sender, SaveContactResult e)
+        private void ContactSaveTaskCompleted(object sender, SaveContactResult e)
         {
             SaveContactTask task = sender as SaveContactTask;
 
             if (e.TaskResult == TaskResult.OK)
             {
-
                 Deployment.Current.Dispatcher.BeginInvoke(() =>
-                {
-                    DeviceContacts deviceContacts = new DeviceContacts();
-                    deviceContacts.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(postAdd_SearchCompleted);
-
-                    string displayName = String.Format("{0}{2}{1}", task.FirstName, task.LastName, String.IsNullOrEmpty(task.FirstName) ? "" : " ");
-
-                    deviceContacts.SearchAsync(displayName, FilterKind.DisplayName, task);
-                });
+                    {
+                        var deviceContacts = new DeviceContacts();
+                        deviceContacts.SearchCompleted +=
+                            postAdd_SearchCompleted;
 
+                        if (task != null)
+                        {
+                            string displayName = String.Format("{0}{2}{1}", task.FirstName, task.LastName,
+                                                               String.IsNullOrEmpty(task.FirstName) ? "" : " ");
 
+                            deviceContacts.SearchAsync(displayName, FilterKind.DisplayName, task);
+                        }
+                    });
             }
             else if (e.TaskResult == TaskResult.Cancel)
             {
@@ -354,18 +383,18 @@ namespace WPCordovaClassLib.Cordova.Commands
             }
         }
 
-        void postAdd_SearchCompleted(object sender, ContactsSearchEventArgs e)
+        private void postAdd_SearchCompleted(object sender, ContactsSearchEventArgs e)
         {
-            if (e.Results.Count() > 0)
+            if (e.Results.Any())
             {
-                List<Contact> foundContacts = new List<Contact>();
+                new List<Contact>();
 
                 int n = (from Contact contact in e.Results select contact.GetHashCode()).Max();
                 Contact newContact = (from Contact contact in e.Results
                                       where contact.GetHashCode() == n
                                       select contact).First();
 
-                DispatchCommandResult(new PluginResult(PluginResult.Status.OK, FormatJSONContact(newContact, null)));
+                DispatchCommandResult(new PluginResult(PluginResult.Status.OK, newContact.ToJson(null)));
             }
             else
             {
@@ -374,13 +403,41 @@ namespace WPCordovaClassLib.Cordova.Commands
         }
 
 
-
         public void remove(string id)
         {
             // note id is wrapped in [] and always has exactly one string ...
             DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "{\"code\":" + NOT_SUPPORTED_ERROR + "}"));
         }
 
+        public void pickContact(string arguments)
+        {
+            string[] args = JSON.JsonHelper.Deserialize<string[]>(arguments);
+
+            // Use custom contact picker because WP8 api doesn't provide its' own
+            // contact picker, only PhoneNumberChooser or EmailAddressChooserTask 
+            var task = new ContactPickerTask();
+            var desiredFields = JSON.JsonHelper.Deserialize<string[]>(args[0]);
+
+            task.Completed += delegate(Object sender, ContactPickerTask.PickResult e)
+                {
+                    if (e.TaskResult == TaskResult.OK)
+                    {
+                        string strResult = e.Contact.ToJson(desiredFields);
+                        var result = new PluginResult(PluginResult.Status.OK)
+                            {
+                                Message = strResult
+                            };
+                        DispatchCommandResult(result);
+                    }
+                    if (e.TaskResult == TaskResult.Cancel)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Operation cancelled."));
+                    }
+                };
+
+            task.Show();
+        }
+
         public void search(string searchCriteria)
         {
             string[] args = JSON.JsonHelper.Deserialize<string[]>(searchCriteria);
@@ -409,7 +466,7 @@ namespace WPCordovaClassLib.Cordova.Commands
             }
 
             DeviceContacts deviceContacts = new DeviceContacts();
-            deviceContacts.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(contacts_SearchCompleted);
+            deviceContacts.SearchCompleted += contacts_SearchCompleted;
 
             // default is to search all fields
             FilterKind filterKind = FilterKind.None;
@@ -432,7 +489,6 @@ namespace WPCordovaClassLib.Cordova.Commands
 
             try
             {
-
                 deviceContacts.SearchAsync(searchParams.options.filter, filterKind, searchParams);
             }
             catch (Exception ex)
@@ -443,7 +499,7 @@ namespace WPCordovaClassLib.Cordova.Commands
 
         private void contacts_SearchCompleted(object sender, ContactsSearchEventArgs e)
         {
-            ContactSearchParams searchParams = (ContactSearchParams)e.State;
+            var searchParams = (ContactSearchParams) e.State;
 
             List<Contact> foundContacts = null;
             // used for comparing strings, ""  instantiates with InvariantCulture
@@ -460,13 +516,17 @@ namespace WPCordovaClassLib.Cordova.Commands
                 {
                     foundContacts.AddRange(from Contact con in e.Results
                                            from ContactEmailAddress a in con.EmailAddresses
-                                           where culture.CompareInfo.IndexOf(a.EmailAddress, searchParams.options.filter, compare_option) >= 0
+                                           where
+                                               culture.CompareInfo.IndexOf(a.EmailAddress, searchParams.options.filter,
+                                                                           compare_option) >= 0
                                            select con);
                 }
                 if (searchParams.fields.Contains("displayName"))
                 {
                     foundContacts.AddRange(from Contact con in e.Results
-                                           where culture.CompareInfo.IndexOf(con.DisplayName, searchParams.options.filter, compare_option) >= 0
+                                           where
+                                               culture.CompareInfo.IndexOf(con.DisplayName, searchParams.options.filter,
+                                                                           compare_option) >= 0
                                            select con);
                 }
                 if (searchParams.fields.Contains("name"))
@@ -488,14 +548,18 @@ namespace WPCordovaClassLib.Cordova.Commands
                 {
                     foundContacts.AddRange(from Contact con in e.Results
                                            from ContactPhoneNumber a in con.PhoneNumbers
-                                           where culture.CompareInfo.IndexOf(a.PhoneNumber, searchParams.options.filter, compare_option) >= 0
+                                           where
+                                               culture.CompareInfo.IndexOf(a.PhoneNumber, searchParams.options.filter,
+                                                                           compare_option) >= 0
                                            select con);
                 }
                 if (searchParams.fields.Contains("urls"))
                 {
                     foundContacts.AddRange(from Contact con in e.Results
                                            from string a in con.Websites
-                                           where culture.CompareInfo.IndexOf(a, searchParams.options.filter, compare_option) >= 0
+                                           where
+                                               culture.CompareInfo.IndexOf(a, searchParams.options.filter,
+                                                                           compare_option) >= 0
                                            select con);
                 }
             }
@@ -504,16 +568,14 @@ namespace WPCordovaClassLib.Cordova.Commands
                 foundContacts = new List<Contact>(e.Results);
             }
 
-            //List<string> contactList = new List<string>();
-
             string strResult = "";
 
             IEnumerable<Contact> distinctContacts = foundContacts.Distinct();
 
             foreach (Contact contact in distinctContacts)
             {
-                strResult += FormatJSONContact(contact, null) + ",";
-                //contactList.Add(FormatJSONContact(contact, null));
+                strResult += contact.ToJson(searchParams.options.desiredFields) + ",";
+
                 if (!searchParams.options.multiple)
                 {
                     break; // just return the first item
@@ -522,172 +584,6 @@ namespace WPCordovaClassLib.Cordova.Commands
             PluginResult result = new PluginResult(PluginResult.Status.OK);
             result.Message = "[" + strResult.TrimEnd(',') + "]";
             DispatchCommandResult(result);
-
-        }
-
-        private string FormatJSONPhoneNumbers(Contact con)
-        {
-            string retVal = "";
-            string contactFieldFormat = "\"type\":\"{0}\",\"value\":\"{1}\",\"pref\":\"false\"";
-            foreach (ContactPhoneNumber number in con.PhoneNumbers)
-            {
-
-                string contactField = string.Format(contactFieldFormat,
-                                                    number.Kind.ToString(),
-                                                    number.PhoneNumber);
-
-                retVal += "{" + contactField + "},";
-            }
-            return retVal.TrimEnd(',');
-        }
-
-        private string FormatJSONEmails(Contact con)
-        {
-            string retVal = "";
-            string contactFieldFormat = "\"type\":\"{0}\",\"value\":\"{1}\",\"pref\":\"false\"";
-            foreach (ContactEmailAddress address in con.EmailAddresses)
-            {
-                string contactField = string.Format(contactFieldFormat,
-                                                    address.Kind.ToString(),
-                                                    EscapeJson(address.EmailAddress));
-
-                retVal += "{" + contactField + "},";
-            }
-            return retVal.TrimEnd(',');
-        }
-
-        private string getFormattedJSONAddress(ContactAddress address, bool isPrefered)
-        {
-
-            string addressFormatString = "\"pref\":{0}," + // bool
-                          "\"type\":\"{1}\"," +
-                          "\"formatted\":\"{2}\"," +
-                          "\"streetAddress\":\"{3}\"," +
-                          "\"locality\":\"{4}\"," +
-                          "\"region\":\"{5}\"," +
-                          "\"postalCode\":\"{6}\"," +
-                          "\"country\":\"{7}\"";
-
-            string formattedAddress = EscapeJson(address.PhysicalAddress.AddressLine1 + " "
-                                    + address.PhysicalAddress.AddressLine2 + " "
-                                    + address.PhysicalAddress.City + " "
-                                    + address.PhysicalAddress.StateProvince + " "
-                                    + address.PhysicalAddress.CountryRegion + " "
-                                    + address.PhysicalAddress.PostalCode);
-
-            string jsonAddress = string.Format(addressFormatString,
-                                               isPrefered ? "\"true\"" : "\"false\"",
-                                               address.Kind.ToString(),
-                                               formattedAddress,
-                                               EscapeJson(address.PhysicalAddress.AddressLine1 + " " + address.PhysicalAddress.AddressLine2),
-                                               address.PhysicalAddress.City,
-                                               address.PhysicalAddress.StateProvince,
-                                               address.PhysicalAddress.PostalCode,
-                                               address.PhysicalAddress.CountryRegion);
-
-            //Debug.WriteLine("getFormattedJSONAddress returning :: " + jsonAddress);
-
-            return "{" + jsonAddress + "}";
-        }
-
-        private string FormatJSONAddresses(Contact con)
-        {
-            string retVal = "";
-            foreach (ContactAddress address in con.Addresses)
-            {
-                retVal += this.getFormattedJSONAddress(address, false) + ",";
-            }
-
-            //Debug.WriteLine("FormatJSONAddresses returning :: " + retVal);
-            return retVal.TrimEnd(',');
-        }
-
-        private string FormatJSONWebsites(Contact con)
-        {
-            string retVal = "";
-            foreach (string website in con.Websites)
-            {
-                retVal += "\"" + EscapeJson(website) + "\",";
-            }
-            return retVal.TrimEnd(',');
-        }
-
-        /*
-         *  formatted: The complete name of the contact. (DOMString)
-            familyName: The contacts family name. (DOMString)
-            givenName: The contacts given name. (DOMString)
-            middleName: The contacts middle name. (DOMString)
-            honorificPrefix: The contacts prefix (example Mr. or Dr.) (DOMString)
-            honorificSuffix: The contacts suffix (example Esq.). (DOMString)
-         */
-        private string FormatJSONName(Contact con)
-        {
-            string retVal = "";
-            string formatStr = "\"formatted\":\"{0}\"," +
-                                "\"familyName\":\"{1}\"," +
-                                "\"givenName\":\"{2}\"," +
-                                "\"middleName\":\"{3}\"," +
-                                "\"honorificPrefix\":\"{4}\"," +
-                                "\"honorificSuffix\":\"{5}\"";
-
-            if (con.CompleteName != null)
-            {
-                retVal = string.Format(formatStr,
-                                   EscapeJson(con.CompleteName.FirstName + " " + con.CompleteName.LastName), // TODO: does this need suffix? middlename?
-                                   EscapeJson(con.CompleteName.LastName),
-                                   EscapeJson(con.CompleteName.FirstName),
-                                   EscapeJson(con.CompleteName.MiddleName),
-                                   EscapeJson(con.CompleteName.Title),
-                                   EscapeJson(con.CompleteName.Suffix));
-            }
-            else
-            {
-                retVal = string.Format(formatStr,"","","","","","");
-            }
-
-            return "{" + retVal + "}";
-        }
-
-        private string FormatJSONContact(Contact con, string[] fields)
-        {
-
-            string contactFormatStr = "\"id\":\"{0}\"," +
-                                      "\"displayName\":\"{1}\"," +
-                                      "\"nickname\":\"{2}\"," +
-                                      "\"phoneNumbers\":[{3}]," +
-                                      "\"emails\":[{4}]," +
-                                      "\"addresses\":[{5}]," +
-                                      "\"urls\":[{6}]," +
-                                      "\"name\":{7}," +
-                                      "\"note\":\"{8}\"," +
-                                      "\"birthday\":\"{9}\"";
-
-
-            string jsonContact = String.Format(contactFormatStr,
-                                               con.GetHashCode(),
-                                               EscapeJson(con.DisplayName),
-                                               EscapeJson(con.CompleteName != null ? con.CompleteName.Nickname : ""),
-                                               FormatJSONPhoneNumbers(con),
-                                               FormatJSONEmails(con),
-                                               FormatJSONAddresses(con),
-                                               FormatJSONWebsites(con),
-                                               FormatJSONName(con),
-                                               EscapeJson(con.Notes.FirstOrDefault()),
-                                               EscapeJson(Convert.ToString(con.Birthdays.FirstOrDefault())));
-
-            return "{" + jsonContact + "}";
-
-        }
-
-        
-        private static string EscapeJson(string str)
-        {
-            if (String.IsNullOrEmpty(str))
-            {
-                return str;
-            }
-
-            return str.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\t", "\\t").Replace("\"", "\\\"").Replace("&", "\\&");
         }
     }
-}
+}
\ No newline at end of file