You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ni...@apache.org on 2015/05/13 23:12:51 UTC

cordova-plugin-contacts git commit: CB-8987: Support for save and remove for Windows 10

Repository: cordova-plugin-contacts
Updated Branches:
  refs/heads/master 6b69ffa34 -> 959fffad4


CB-8987: Support for save and remove for Windows 10


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/959fffad
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/tree/959fffad
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/diff/959fffad

Branch: refs/heads/master
Commit: 959fffad4a1e7c27b16478b24cdc628200d718d4
Parents: 6b69ffa
Author: Rob Paveza <Ro...@microsoft.com>
Authored: Fri May 8 13:19:25 2015 -0700
Committer: Rob Paveza <Ro...@microsoft.com>
Committed: Fri May 8 13:19:25 2015 -0700

----------------------------------------------------------------------
 README.md                   |   8 +-
 plugin.xml                  |   3 +
 src/windows/ContactProxy.js | 222 +++++++++++++++++++++++++++++++++++++--
 tests/tests.js              |  52 ++++++++-
 4 files changed, 270 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/959fffad/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index c2f6cd0..0c4a81a 100644
--- a/README.md
+++ b/README.md
@@ -69,9 +69,11 @@ __WARNING__: All privileged apps enforce [Content Security Policy](https://devel
 
 ### Windows Quirks
 
-Any contacts returned from `find` and `pickContact` methods are readonly, so your application cannot modify them.
+**Prior to Windows 10:** Any contacts returned from `find` and `pickContact` methods are readonly, so your application cannot modify them.
 `find` method available only on Windows Phone 8.1 devices.
 
+**Windows 10 and above:** Contacts may be saved and will be saved to app-local contacts storage.  Contacts may also be deleted.
+
 ### 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.
@@ -159,7 +161,7 @@ parameter to control which contact properties must be returned back.
 - Firefox OS
 - iOS
 - Windows Phone 7 and 8
-- Windows (Windows Phone 8.1 devices only)
+- Windows (Windows Phone 8.1 and Windows 10)
 
 ### Example
 
@@ -377,7 +379,7 @@ for details.
 
 - __categories__: Not supported, returning `null`.
 
-- __remove__: Method is not supported
+- __remove__: Method is only supported in Windows 10 or above.
 
 ## ContactAddress
 

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/959fffad/plugin.xml
----------------------------------------------------------------------
diff --git a/plugin.xml b/plugin.xml
index 8fa1fe4..fd40c6f 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -228,6 +228,9 @@
         <config-file target="package.phone.appxmanifest" parent="/Package/Capabilities">
             <m3:Capability Name="contacts" />
         </config-file>
+        <config-file target="package.appxmanifest" parent="/Package/Capabilities" versions=">=10.0.0">
+            <uap:Capability Name="contacts" />
+        </config-file>
     </platform>
 
 </plugin>

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/959fffad/src/windows/ContactProxy.js
----------------------------------------------------------------------
diff --git a/src/windows/ContactProxy.js b/src/windows/ContactProxy.js
index dc4ba54..04b6e55 100644
--- a/src/windows/ContactProxy.js
+++ b/src/windows/ContactProxy.js
@@ -31,17 +31,19 @@ function convertToContact(windowsContact) {
     var contact = new Contact();
 
     // displayName & nickname
-    contact.displayName = windowsContact.name || windowsContact.displayName;
+    contact.displayName = windowsContact.displayName || windowsContact.name;
     contact.nickname = windowsContact.name;
+    contact.id = windowsContact.id;
 
     // name
     // Additional fields like lastName, middleName etc. available on windows8.1/wp8.1 only
     contact.name = new ContactName(
-        windowsContact.name || windowsContact.displayName,
+        windowsContact.displayName || windowsContact.name,
         windowsContact.lastName,
+        windowsContact.firstName || windowsContact.name,
         windowsContact.middleName,
-        windowsContact.honorificPrefix,
-        windowsContact.honorificSuffix);
+        windowsContact.honorificNamePrefix || windowsContact.honorificPrefix,
+        windowsContact.honorificNameSuffix || windowsContact.honorificSuffix);
 
     // phoneNumbers
     contact.phoneNumbers = [];
@@ -116,6 +118,105 @@ function convertToContact(windowsContact) {
 // Win API Contacts namespace
 var contactsNS = Windows.ApplicationModel.Contacts;
 
+function cdvContactToWindowsContact(contact) {
+    var result = new contactsNS.Contact();
+    
+    // name first
+    if (contact.name) {
+        result.displayNameOverride = contact.name.formatted;
+        result.firstName = contact.name.givenName;
+        result.middleName = contact.name.middleName;
+        result.lastName = contact.name.familyName;
+        result.honorificNamePrefix = contact.name.honorificPrefix;
+        result.honorificNameSuffix = contact.name.honorificSuffix;
+    }
+    
+    result.nickname = contact.nickname;
+    
+    // phone numbers
+    if (contact.phoneNumbers) {
+        contact.phoneNumbers.forEach(function(contactPhone) {
+            var resultPhone = new contactsNS.ContactPhone();
+            resultPhone.description = contactPhone.type;
+            resultPhone.number = contactPhone.value;
+            result.phones.push(resultPhone);
+        });
+    }
+    
+    // emails
+    if (contact.emails) {
+        contact.emails.forEach(function(contactEmail) {
+            var resultEmail = new contactsNS.ContactEmail();
+            resultEmail.description = contactEmail.type;
+            resultEmail.address = contactEmail.value;
+            result.emails.push(resultEmail);
+        });
+    }
+    
+    // Addresses
+    if (contact.addresses) {
+        contact.addresses.forEach(function(contactAddress) {
+            var address = new contactsNS.ContactAddress();
+            address.description = contactAddress.type;
+            address.streetAddress = contactAddress.streetAddress;
+            address.locality = contactAddress.locality;
+            address.region = contactAddress.region;
+            address.postalCode = contactAddress.postalCode;
+            address.country = contactAddress.country;
+            result.addresses.push(address);
+        });
+    }
+    
+    // IMs
+    if (contact.ims) {
+        contact.ims.forEach(function(contactIM) {
+            var acct = new contactsNS.ContactConnectedServiceAccount();
+            acct.serviceName = contactIM.type;
+            acct.id = contactIM.value;
+            result.connectedServiceAccounts.push(acct);
+        });
+    }
+    
+    // JobInfo 
+    if (contact.organizations) {
+        contact.organizations.forEach(function(contactOrg) {
+            var job = new contactsNS.ContactJobInfo();
+            job.companyName = contactOrg.name;
+            job.department = contactOrg.department;
+            job.description = contactOrg.type;
+            job.title = contactOrg.title;
+            result.jobInfo.push(job);
+        });
+    }
+    
+    result.notes = contact.note;
+    
+    if (contact.photos) {
+        var eligiblePhotos = contact.photos.filter(function(photo) { 
+            return typeof photo.value !== 'undefined';
+        });
+        if (eligiblePhotos.length > 0) {
+            var supportedPhoto = eligiblePhotos[0];
+            var path = supportedPhoto.value;
+            
+            try {
+                var streamRef;
+                if (/^([a-z][a-z0-9+\-.]*):\/\//i.test(path)) {
+                    streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(new Windows.Foundation.Uri(path));
+                } else {
+                    streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(path);
+                }
+                result.thumbnail = streamRef;
+            }
+            catch (e) {
+                // incompatible reference to the photo
+            }
+        }
+    }
+    
+    return result;
+}
+
 module.exports = {
 
     pickContact: function (win, fail, args) {
@@ -164,13 +265,112 @@ module.exports = {
     },
 
     save: function (win, fail, args) {
-        // Not supported yet since WinJS API do not provide methods to manage contactStore
-        // On Windows Phone 8.1 this can be implemented using native class library 
-        // See Windows.Phone.PersonalInformation namespace
-        // http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.phone.personalinformation.aspx
-
-        //We don't need to create Error object here since it will be created at navigator.contacts.find() method
-        fail && fail(ContactError.NOT_SUPPORTED_ERROR);
+        if (typeof contactsNS.ContactList === 'undefined') {
+            // Not supported yet since WinJS API do not provide methods to manage contactStore
+            // On Windows Phone 8.1 this can be implemented using native class library 
+            // See Windows.Phone.PersonalInformation namespace
+            // http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.phone.personalinformation.aspx
+    
+            //We don't need to create Error object here since it will be created at navigator.contacts.find() method
+            fail && fail(ContactError.NOT_SUPPORTED_ERROR);
+            return;
+        }
+        
+        var winContact = cdvContactToWindowsContact(args[0]);
+        
+        contactsNS.ContactManager.requestStoreAsync(contactsNS.ContactStoreAccessType.appContactsReadWrite).then(function(store) {
+                return store.findContactListsAsync().then(function(lists) {
+                        if (lists.length > 0) {
+                            return lists[0];    
+                        } else {
+                            return store.createContactListAsync('');
+                        }
+                    }, function(error) {
+                        return store.createContactListAsync('');
+                    });
+            }).then(function(list) {
+                return list.saveContactAsync(winContact);
+            }).done(function(result) {
+                win(convertToContact(winContact));
+            }, function(error) {
+                fail(error);
+            });
+    },
+    
+    remove: function(win, fail, args) {
+        if (typeof contactsNS.ContactList === 'undefined') {
+            // Not supported yet since WinJS API do not provide methods to manage contactStore
+            // On Windows Phone 8.1 this can be implemented using native class library 
+            // See Windows.Phone.PersonalInformation namespace
+            // http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.phone.personalinformation.aspx
+    
+            //We don't need to create Error object here since it will be created at navigator.contacts.find() method
+            fail && fail(ContactError.NOT_SUPPORTED_ERROR);
+            return;
+        }
+        
+        // This is a complicated scenario because in Win10, there is a notion of 'app contacts' vs 'global contacts'.
+        // search() returns all global contacts, which are "aggregate contacts", so the IDs of contacts that Cordova
+        // creates never match the IDs of the contacts returned from search().
+        // In order to work around this, we need to:
+        //  - Get two Stores: one that is read-write to the app-contacts list, one which is read-only for global contacts  
+        //  - Read the app-local store to see if a contact with the passed-in ID matches
+        //  - Grab the global aggregate contact manager, then ask it for raw contacts (app-local ACM returns access denied)
+        //  - Find my app-list of contacts
+        //  - Enumerate the raw contacts and see if there is a raw contact whose parent list matches the app-list
+        //  - If so, remove the raw contact from the app-list
+        //  - If any of this fails, the operation fails
+        WinJS.Promise.join([contactsNS.ContactManager.requestStoreAsync(contactsNS.ContactStoreAccessType.appContactsReadWrite), 
+                            contactsNS.ContactManager.requestStoreAsync(contactsNS.ContactStoreAccessType.allContactsReadOnly)]).then(function(stores) {
+                var readOnlyStore = stores[1];
+                var writableStore = stores[0];
+                
+                var storeReader = writableStore.getContactReader();
+                return storeReader.readBatchAsync().then(function(batch) {
+                    if (batch.status !== contactsNS.ContactBatchStatus.success) {
+                        // Couldn't read contacts store
+                        throw new ContactError(ContactError.IO_ERROR);
+                    }
+                    
+                    var candidates = batch.contacts.filter(function(testContact) {
+                        return testContact.id === args[0];
+                    });
+                    
+                    if (candidates.length === 0) {
+                        // No matching contact from aggregate store
+                        throw new ContactError(ContactError.IO_ERROR);
+                    }
+                    
+                    return candidates[0];
+                }).then(function(contactToDelete) {
+                    return readOnlyStore.aggregateContactManager.findRawContactsAsync(contactToDelete);
+                }).then(function(rawContacts) {
+                    return writableStore.findContactListsAsync().then(function(lists) {
+                        var deleteList = null;
+                        var deleteContact = null;
+                        var matched = lists.some(function(list) {
+                            for (var i = 0; i < rawContacts.length; i++) {
+                                if (rawContacts[i].contactListId === list.id) {
+                                    deleteList = list;
+                                    deleteContact = rawContacts[i];
+                                    return true;
+                                }
+                            }
+                            return false;
+                        });
+                        
+                        if (!matched) {
+                            throw new ContactError(ContactError.IO_ERROR);
+                        }
+                        
+                        return deleteList.deleteContactAsync(deleteContact);
+                    });
+                });
+            }).done(function() {
+                win();
+            }, function(error) {
+                fail(error);
+            });
     },
 
     search: function (win, fail, options) {

http://git-wip-us.apache.org/repos/asf/cordova-plugin-contacts/blob/959fffad/tests/tests.js
----------------------------------------------------------------------
diff --git a/tests/tests.js b/tests/tests.js
index 46d9c4e..f8b5615 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -515,6 +515,50 @@ exports.defineManualTests = function (contentEl, createActionButton) {
             alert(e);
         }
     }
+    
+    function removeDooneyEvans() {
+        var results = document.getElementById('contact_results');
+        
+        navigator.contacts.find(["displayName", "name", "phoneNumbers", "emails", "urls", "note"], function(contacts) {
+            var removes = [];
+            contacts.forEach(function(contact) {
+                if (contact.name.formatted.indexOf('Dooney Evans') > -1) {
+                    removes.push(contact);
+                }
+            });
+            
+            var nextToRemove = undefined;
+            if (removes.length > 0) {
+              nextToRemove = removes.shift();
+            }
+            function removeNext(item) {
+                if (typeof item === 'undefined')
+                    return;
+                
+                if (removes.length > 0) {
+                    nextToRemove = removes.shift();
+                } else {
+                  nextToRemove = undefined;
+                }
+                
+                item.remove(function removeSucceeded() {
+                    results.innerHTML += '<br>Removed contact with ID ' + item.id;
+                    removeNext(nextToRemove);
+                }, function removeFailed(e) {
+                    results.innerHTML += '<br>Remove failed contact with ID ' + item.id;
+                    removeNext(nextToRemove);
+                });
+            }
+            removeNext(nextToRemove);
+        }, function (e) {
+            if (e.code === ContactError.NOT_SUPPORTED_ERROR) {
+                results.innerHTML = 'Searching for contacts is not supported.';
+            }
+            else {
+                results.innerHTML = 'Search failed: error ' + e.code;
+            }
+        })
+    }
 
     /******************************************************************************/
 
@@ -525,7 +569,9 @@ exports.defineManualTests = function (contentEl, createActionButton) {
         '<div id="get_contacts"></div>' +
         'Expected result: Status box will show number of contacts and list them. May be empty on a fresh device until you click Add.' +
         '</p> <div id="add_contact"></div>' +
-        'Expected result: Will add a new contact. Log will say "Contact saved." or "Saving contacts not supported." if not supported on current platform. Verify by running Get phone contacts again';
+        'Expected result: Will add a new contact. Log will say "Contact saved." or "Saving contacts not supported." if not supported on current platform. Verify by running Get phone contacts again' +
+        '<div id="remove_dooney_evans"></div>' + 
+        '<p>Expected result: Will remove any contacts named "Dooney Evans".  Log will output success or failure, plus ID, or fail like getting contacts will fail.</p>';
 
     createActionButton("Get phone's contacts", function () {
         getContacts();
@@ -534,4 +580,8 @@ exports.defineManualTests = function (contentEl, createActionButton) {
     createActionButton("Add a new contact 'Dooney Evans'", function () {
         addContact();
     }, 'add_contact');
+    
+    createActionButton("Delete all 'Dooney Evans'", function() {
+        removeDooneyEvans();
+    }, 'remove_dooney_evans');
 };


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org