From 72b2ec804cdbb029da5c4e1a5f5a176d9637ba0a Mon Sep 17 00:00:00 2001 From: macdonst Date: Thu, 7 Oct 2010 06:13:04 +0800 Subject: [PATCH 1/4] Removing unused functions --- framework/assets/js/contact.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/framework/assets/js/contact.js b/framework/assets/js/contact.js index 2d735027..d24885da 100644 --- a/framework/assets/js/contact.js +++ b/framework/assets/js/contact.js @@ -109,15 +109,6 @@ Contacts.prototype.create = function(properties) { return contact; }; -Contacts.prototype.droidDone = function(contacts) { - this.win(eval('(' + contacts + ')')); -}; - -Contacts.prototype.m_foundContacts = function(win, contacts) { - this.inProgress = false; - win(contacts); -}; - var ContactFindOptions = function(filter, multiple, limit, updatedSince) { this.filter = filter || ''; this.multiple = multiple || true; From 50ab0e083487fd9602e9edb3bf28c68e96737d14 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 8 Oct 2010 09:18:10 -0500 Subject: [PATCH 2/4] Add confirm, start/stopActivity, start/stopProgress to notification service. Add "application loading" spinner that user can optionally show when app is starting. --- framework/assets/js/notification.js | 69 ++++-- framework/assets/js/phonegap.js.base | 3 + framework/src/com/phonegap/DroidGap.java | 2 +- framework/src/com/phonegap/Notification.java | 223 ++++++++++++++++++- 4 files changed, 280 insertions(+), 17 deletions(-) diff --git a/framework/assets/js/notification.js b/framework/assets/js/notification.js index 2d783211..92934ccb 100755 --- a/framework/assets/js/notification.js +++ b/framework/assets/js/notification.js @@ -6,40 +6,85 @@ function Notification() { /** * Open a native alert dialog, with a customizable title and button text. - * @param {String} message Message to print in the body of the alert - * @param {String} [title="Alert"] Title of the alert dialog (default: Alert) - * @param {String} [buttonLabel="OK"] Label of the close button (default: OK) + * + * @param {String} message Message to print in the body of the alert + * @param {String} title Title of the alert dialog (default: Alert) + * @param {String} buttonLabel Label of the close button (default: OK) */ Notification.prototype.alert = function(message, title, buttonLabel) { - var _title = (title || "Alert"); - var _buttonLabel = (buttonLabel || "OK"); + var _title = (title || "Alert"); + var _buttonLabel = (buttonLabel || "OK"); PhoneGap.execAsync(null, null, "Notification", "alert", [message,_title,_buttonLabel]); }; +/** + * Open a native confirm dialog, with a customizable title and button text. + * + * @param {String} message Message to print in the body of the alert + * @param {String} title Title of the alert dialog (default: Confirm) + * @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel') + * @return {Number} The index of the button clicked + */ +Notification.prototype.confirm = function(message, title, buttonLabels) { + var _title = (title || "Confirm"); + var _buttonLabels = (buttonLabels || "OK,Cancel"); + return PhoneGap.execAsync(null, null, "Notification", "confirm", [message,_title,_buttonLabels]); +}; + /** * Start spinning the activity indicator on the statusbar */ Notification.prototype.activityStart = function() { + PhoneGap.execAsync(null, null, "Notification", "activityStart", ["Busy","Please wait..."]); }; /** * Stop spinning the activity indicator on the statusbar, if it's currently spinning */ Notification.prototype.activityStop = function() { + PhoneGap.execAsync(null, null, "Notification", "activityStop", []); +}; + +/** + * Display a progress dialog with progress bar that goes from 0 to 100. + * + * @param {String} title Title of the progress dialog. + * @param {String} message Message to display in the dialog. + */ +Notification.prototype.progressStart = function(title, message) { + PhoneGap.execAsync(null, null, "Notification", "progressStart", [title, message]); +}; + +/** + * Set the progress dialog value. + * + * @param {Number} value 0-100 + */ +Notification.prototype.progressValue = function(value) { + PhoneGap.execAsync(null, null, "Notification", "progressValue", [value]); +}; + +/** + * Close the progress dialog. + */ +Notification.prototype.progressStop = function() { + PhoneGap.execAsync(null, null, "Notification", "progressStop", []); }; /** * Causes the device to blink a status LED. - * @param {Integer} count The number of blinks. - * @param {String} colour The colour of the light. + * + * @param {Integer} count The number of blinks. + * @param {String} colour The colour of the light. */ Notification.prototype.blink = function(count, colour) { - + // NOT IMPLEMENTED }; /** * Causes the device to vibrate. - * @param {Integer} mills The number of milliseconds to vibrate for. + * + * @param {Integer} mills The number of milliseconds to vibrate for. */ Notification.prototype.vibrate = function(mills) { PhoneGap.execAsync(null, null, "Notification", "vibrate", [mills]); @@ -47,16 +92,14 @@ Notification.prototype.vibrate = function(mills) { /** * Causes the device to beep. - * On Android, the default notification ringtone is played. + * On Android, the default notification ringtone is played "count" times. * - * @param {Integer} count The number of beeps. + * @param {Integer} count The number of beeps. */ Notification.prototype.beep = function(count) { PhoneGap.execAsync(null, null, "Notification", "beep", [count]); }; -// TODO: of course on Blackberry and Android there notifications in the UI as well - PhoneGap.addConstructor(function() { if (typeof navigator.notification == "undefined") navigator.notification = new Notification(); }); diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index ab71d3d7..59401348 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -245,6 +245,9 @@ PhoneGap.Channel.join(function() { * received from native side. */ PhoneGap.Channel.join(function() { + // Turn off app loading dialog + navigator.notification.activityStop(); + PhoneGap.onDeviceReady.fire(); // Fire the onresume event, since first one happens before JavaScript is loaded diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 3c7cabc6..46697229 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -84,7 +84,7 @@ public class DroidGap extends Activity { private BrowserKey mKey; public CallbackServer callbackServer; - private PluginManager pluginManager; + protected PluginManager pluginManager; private String url; // The initial URL for our app private String baseUrl; // The base of the initial URL for our app diff --git a/framework/src/com/phonegap/Notification.java b/framework/src/com/phonegap/Notification.java index 3d76366f..95874a0e 100755 --- a/framework/src/com/phonegap/Notification.java +++ b/framework/src/com/phonegap/Notification.java @@ -5,6 +5,7 @@ import org.json.JSONException; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.media.Ringtone; @@ -16,7 +17,11 @@ import android.os.Vibrator; * This class provides access to notifications on the device. */ public class Notification extends Plugin { - + + public int confirmResult = -1; + public ProgressDialog spinnerDialog = null; + public ProgressDialog progressDialog = null; + /** * Constructor. */ @@ -45,6 +50,25 @@ public class Notification extends Plugin { else if (action.equals("alert")) { this.alert(args.getString(0),args.getString(1),args.getString(2)); } + else if (action.equals("confirm")) { + int i = this.confirm(args.getString(0),args.getString(1),args.getString(2)); + return new PluginResult(status, i); + } + else if (action.equals("activityStart")) { + this.activityStart(args.getString(0),args.getString(1)); + } + else if (action.equals("activityStop")) { + this.activityStop(); + } + else if (action.equals("progressStart")) { + this.progressStart(args.getString(0),args.getString(1)); + } + else if (action.equals("progressValue")) { + this.progressValue(args.getInt(0)); + } + else if (action.equals("progressStop")) { + this.progressStop(); + } return new PluginResult(status, result); } catch (JSONException e) { return new PluginResult(PluginResult.Status.JSON_EXCEPTION); @@ -58,10 +82,30 @@ public class Notification extends Plugin { * @return T=returns value */ public boolean isSynch(String action) { - if(action.equals("alert")) + if (action.equals("alert")) { return true; - else + } + else if (action.equals("confirm")) { + return true; + } + else if (action.equals("activityStart")) { + return true; + } + else if (action.equals("activityStop")) { + return true; + } + else if (action.equals("progressStart")) { + return true; + } + else if (action.equals("progressValue")) { + return true; + } + else if (action.equals("progressStop")) { + return true; + } + else { return false; + } } //-------------------------------------------------------------------------- @@ -128,4 +172,177 @@ public class Notification extends Plugin { dlg.show(); } + /** + * Builds and shows a native Android confirm dialog with given title, message, buttons. + * This dialog only shows up to 3 buttons. Any labels after that will be ignored. + * + * @param message The message the dialog should display + * @param title The title of the dialog + * @param buttonLabels A comma separated list of button labels (Up to 3 buttons) + * @return The index of the button clicked (1,2 or 3) + */ + public synchronized int confirm(final String message, final String title, String buttonLabels) { + + // Create dialog on UI thread + final DroidGap ctx = this.ctx; + final Notification notification = this; + final String[] fButtons = buttonLabels.split(","); + Runnable runnable = new Runnable() { + public void run() { + AlertDialog.Builder dlg = new AlertDialog.Builder(ctx); + dlg.setMessage(message); + dlg.setTitle(title); + dlg.setCancelable(false); + + // First button + if (fButtons.length > 0) { + dlg.setPositiveButton(fButtons[0], + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + synchronized(notification) { + notification.confirmResult = 1; + notification.notifyAll(); + } + } + }); + } + + // Second button + if (fButtons.length > 1) { + dlg.setNeutralButton(fButtons[1], + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + synchronized(notification) { + notification.confirmResult = 2; + notification.notifyAll(); + } + } + }); + } + + // Third button + if (fButtons.length > 2) { + dlg.setNegativeButton(fButtons[2], + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + synchronized(notification) { + notification.confirmResult = 3; + notification.notifyAll(); + } + } + } + ); + } + + dlg.create(); + dlg.show(); + } + }; + this.ctx.runOnUiThread(runnable); + + // Wait for dialog to close + synchronized(runnable) { + try { + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return this.confirmResult; + } + + /** + * Show the spinner. + * + * @param title Title of the dialog + * @param message The message of the dialog + */ + public synchronized void activityStart(final String title, final String message) { + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } + final Notification notification = this; + final DroidGap ctx = this.ctx; + Runnable runnable = new Runnable() { + public void run() { + notification.spinnerDialog = ProgressDialog.show(ctx, title , message, true, true, + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + notification.spinnerDialog = null; + } + }); + } + }; + this.ctx.runOnUiThread(runnable); + } + + /** + * Stop spinner. + */ + public synchronized void activityStop() { + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } + } + + /** + * Show the progress dialog. + * + * @param title Title of the dialog + * @param message The message of the dialog + */ + public synchronized void progressStart(final String title, final String message) { + if (this.progressDialog != null) { + this.progressDialog.dismiss(); + this.progressDialog = null; + } + final Notification notification = this; + final DroidGap ctx = this.ctx; + Runnable runnable = new Runnable() { + public void run() { + notification.progressDialog = new ProgressDialog(ctx); + notification.progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + notification.progressDialog.setTitle(title); + notification.progressDialog.setMessage(message); + notification.progressDialog.setCancelable(true); + notification.progressDialog.setMax(100); + notification.progressDialog.setProgress(0); + notification.progressDialog.setOnCancelListener( + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + notification.progressDialog = null; + } + }); + notification.progressDialog.show(); + } + }; + this.ctx.runOnUiThread(runnable); + } + + /** + * Set value of progress bar. + * + * @param value 0-100 + */ + public synchronized void progressValue(int value) { + if (this.progressDialog != null) { + this.progressDialog.setProgress(value); + } + } + + /** + * Stop progress dialog. + */ + public synchronized void progressStop() { + if (this.progressDialog != null) { + this.progressDialog.dismiss(); + this.progressDialog = null; + } + } + } From 4a6105de6b6c704e68d9c3f5eefb543fd0c33042 Mon Sep 17 00:00:00 2001 From: macdonst Date: Fri, 8 Oct 2010 03:25:56 +0800 Subject: [PATCH 3/4] Do one table query per contact --- .../src/com/phonegap/ContactAccessorSdk5.java | 484 +++++++----------- 1 file changed, 194 insertions(+), 290 deletions(-) diff --git a/framework/src/com/phonegap/ContactAccessorSdk5.java b/framework/src/com/phonegap/ContactAccessorSdk5.java index b4ab52a6..55cbf5ee 100644 --- a/framework/src/com/phonegap/ContactAccessorSdk5.java +++ b/framework/src/com/phonegap/ContactAccessorSdk5.java @@ -55,7 +55,6 @@ import android.webkit.WebView; */ public class ContactAccessorSdk5 extends ContactAccessor { - private static final String WHERE_STRING = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?"; private static final Map dbMap = new HashMap(); static { dbMap.put("id", ContactsContract.Contacts._ID); @@ -142,59 +141,109 @@ public class ContactAccessorSdk5 extends ContactAccessor { Iterator it = contactIds.iterator(); JSONArray contacts = new JSONArray(); - JSONObject contact; + String contactId; int pos = 0; - String[] events = null; + boolean firstRow = true; while (it.hasNext() && (pos < limit)) { - contact = new JSONObject(); + JSONObject contact = new JSONObject(); + JSONArray organizations = new JSONArray(); + JSONArray addresses = new JSONArray(); + JSONArray phones = new JSONArray(); + JSONArray emails = new JSONArray(); + JSONArray ims = new JSONArray(); + JSONArray websites = new JSONArray(); + JSONArray relationships = new JSONArray(); + contactId = it.next(); - try { - contact.put("id", contactId); - if (isRequired("displayName",populate)) { - contact.put("displayName", displayNameQuery(cr, contactId)); + Cursor c = cr.query(ContactsContract.Data.CONTENT_URI, + null, + ContactsContract.Data.CONTACT_ID + " = ?", + new String[] {contactId}, + null); + + String mimetype = ""; + while (c.moveToNext()) { + try { + if (firstRow) { + firstRow = false; + contact.put("id", contactId); + contact.put("displayName", c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))); + } + mimetype = c.getString(c.getColumnIndex(ContactsContract.Data.MIMETYPE)); + if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + && isRequired("name",populate)) { + contact.put("name", nameQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + && isRequired("phoneNumbers",populate)) { + phones.put(phoneQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + && isRequired("emails",populate)) { + emails.put(emailQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + && isRequired("addresses",populate)) { + addresses.put(addressQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + && isRequired("organizations",populate)) { + organizations.put(organizationQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) + && isRequired("ims",populate)) { + ims.put(imQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) + && isRequired("note",populate)) { + contact.put("note",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE))); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + && isRequired("nickname",populate)) { + contact.put("nickname",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME))); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) + && isRequired("urls",populate)) { + websites.put(websiteQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE) + && isRequired("relationships",populate)) { + relationships.put(relationshipQuery(c)); + } + if (mimetype.equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)) { + if (ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) + && isRequired("anniversary",populate)) { + contact.put("anniversary", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); + } + else if (ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) + && isRequired("birthday",populate)) { + contact.put("birthday", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); + } + } } - if (isRequired("name",populate)) { - contact.put("name", nameQuery(cr, contactId)); + catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(),e); } - if (isRequired("phoneNumbers",populate)) { - contact.put("phoneNumbers", phoneQuery(cr, contactId)); - } - if (isRequired("emails",populate)) { - contact.put("emails", emailQuery(cr, contactId)); - } - if (isRequired("addresses",populate)) { - contact.put("addresses", addressQuery(cr, contactId)); - } - if (isRequired("organizations",populate)) { - contact.put("organizations", organizationQuery(cr, contactId)); - } - if (isRequired("ims",populate)) { - contact.put("ims",imQuery(cr, contactId)); - } - if (isRequired("note",populate)) { - contact.put("note",noteQuery(cr, contactId)); - } - if (isRequired("nickname",populate)) { - contact.put("nickname",nicknameQuery(cr, contactId)); - } - if (isRequired("urls",populate)) { - contact.put("urls",websiteQuery(cr, contactId)); - } - if (isRequired("relationships",populate)) { - contact.put("relationships",relationshipQuery(cr, contactId)); - } - if (isRequired("birthday",populate) || isRequired("anniversary",populate)) { - events = eventQuery(cr, contactId); - contact.put("birthday",events[0]); - contact.put("anniversary",events[1]); - } - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); } - Log.d(LOG_TAG, "putting in contact ID = " + contactId); - + c.close(); + + firstRow = true; + + // Populate the Contact object with it's arrays + try { + contact.put("organizations", organizations); + contact.put("addresses", addresses); + contact.put("phoneNumbers", phones); + contact.put("emails", emails); + contact.put("ims", ims); + contact.put("websites", websites); + contact.put("relationships", relationships); + } + catch (JSONException e) { + Log.e(LOG_TAG,e.getMessage(),e); + } contacts.put(contact); pos++; } @@ -215,7 +264,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { new String[] {searchTerm}); return contactIds; } - + String key; try { for (int i=0; i Date: Fri, 8 Oct 2010 23:04:49 +0800 Subject: [PATCH 4/4] Reduced everything to single database query --- .../src/com/phonegap/ContactAccessor.java | 17 + .../src/com/phonegap/ContactAccessorSdk5.java | 439 ++++++++++-------- 2 files changed, 252 insertions(+), 204 deletions(-) diff --git a/framework/src/com/phonegap/ContactAccessor.java b/framework/src/com/phonegap/ContactAccessor.java index e276118c..fab36964 100644 --- a/framework/src/com/phonegap/ContactAccessor.java +++ b/framework/src/com/phonegap/ContactAccessor.java @@ -159,4 +159,21 @@ public abstract class ContactAccessor { * Handles removing a contact from the database. */ public abstract boolean remove(String id); + + class WhereOptions { + private String where; + private String[] whereArgs; + public void setWhere(String where) { + this.where = where; + } + public String getWhere() { + return where; + } + public void setWhereArgs(String[] whereArgs) { + this.whereArgs = whereArgs; + } + public String[] getWhereArgs() { + return whereArgs; + } + } } \ No newline at end of file diff --git a/framework/src/com/phonegap/ContactAccessorSdk5.java b/framework/src/com/phonegap/ContactAccessorSdk5.java index 55cbf5ee..933a8b43 100644 --- a/framework/src/com/phonegap/ContactAccessorSdk5.java +++ b/framework/src/com/phonegap/ContactAccessorSdk5.java @@ -17,20 +17,16 @@ package com.phonegap; +import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.Map; -import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; -import android.content.ContentResolver; import android.database.Cursor; -import android.net.Uri; import android.provider.ContactsContract; import android.util.Log; import android.webkit.WebView; @@ -112,7 +108,11 @@ public class ContactAccessorSdk5 extends ContactAccessor { } @Override - public JSONArray search(JSONArray filter, JSONObject options) { + public JSONArray search(JSONArray fields, JSONObject options) { + long totalEnd; + long totalStart = System.currentTimeMillis(); + + // Get the find options String searchTerm = ""; int limit = Integer.MAX_VALUE; boolean multiple = true; @@ -131,252 +131,283 @@ public class ContactAccessorSdk5 extends ContactAccessor { } catch (JSONException e) { Log.e(LOG_TAG, e.getMessage(), e); } - - // Get a cursor by creating the query. - ContentResolver cr = mApp.getContentResolver(); - - Set contactIds = buildSetOfContactIds(filter, searchTerm); - HashMap populate = buildPopulationSet(filter); - - Iterator it = contactIds.iterator(); - - JSONArray contacts = new JSONArray(); - String contactId; - int pos = 0; - boolean firstRow = true; - while (it.hasNext() && (pos < limit)) { - JSONObject contact = new JSONObject(); - JSONArray organizations = new JSONArray(); - JSONArray addresses = new JSONArray(); - JSONArray phones = new JSONArray(); - JSONArray emails = new JSONArray(); - JSONArray ims = new JSONArray(); - JSONArray websites = new JSONArray(); - JSONArray relationships = new JSONArray(); + // Loop through the fields the user provided to see what data should be returned. + HashMap populate = buildPopulationSet(fields); + + // Build the ugly where clause and where arguments for one big query. + WhereOptions whereOptions = buildWhereClause(fields, searchTerm); - contactId = it.next(); - - Cursor c = cr.query(ContactsContract.Data.CONTENT_URI, - null, - ContactsContract.Data.CONTACT_ID + " = ?", - new String[] {contactId}, - null); - - String mimetype = ""; - while (c.moveToNext()) { - try { - if (firstRow) { - firstRow = false; - contact.put("id", contactId); - contact.put("displayName", c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))); - } - mimetype = c.getString(c.getColumnIndex(ContactsContract.Data.MIMETYPE)); - if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) - && isRequired("name",populate)) { - contact.put("name", nameQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) - && isRequired("phoneNumbers",populate)) { - phones.put(phoneQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) - && isRequired("emails",populate)) { - emails.put(emailQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) - && isRequired("addresses",populate)) { - addresses.put(addressQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) - && isRequired("organizations",populate)) { - organizations.put(organizationQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) - && isRequired("ims",populate)) { - ims.put(imQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) - && isRequired("note",populate)) { - contact.put("note",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE))); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) - && isRequired("nickname",populate)) { - contact.put("nickname",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME))); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) - && isRequired("urls",populate)) { - websites.put(websiteQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE) - && isRequired("relationships",populate)) { - relationships.put(relationshipQuery(c)); - } - if (mimetype.equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)) { - if (ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) - && isRequired("anniversary",populate)) { - contact.put("anniversary", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); - } - else if (ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) - && isRequired("birthday",populate)) { - contact.put("birthday", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); - } - } - } - catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(),e); - } - } - c.close(); - - firstRow = true; - - // Populate the Contact object with it's arrays + // Get all the rows where the search term matches the fields passed in. + Cursor c = mApp.getContentResolver().query(ContactsContract.Data.CONTENT_URI, + null, + whereOptions.getWhere(), + whereOptions.getWhereArgs(), + ContactsContract.Data.CONTACT_ID + " ASC"); + + String contactId = ""; + String oldContactId = ""; + boolean newContact = true; + String mimetype = ""; + + JSONArray contacts = new JSONArray(); + JSONObject contact = new JSONObject(); + JSONArray organizations = new JSONArray(); + JSONArray addresses = new JSONArray(); + JSONArray phones = new JSONArray(); + JSONArray emails = new JSONArray(); + JSONArray ims = new JSONArray(); + JSONArray websites = new JSONArray(); + JSONArray relationships = new JSONArray(); + + while (c.moveToNext() && (contacts.length() < (limit-1))) { try { - contact.put("organizations", organizations); - contact.put("addresses", addresses); - contact.put("phoneNumbers", phones); - contact.put("emails", emails); - contact.put("ims", ims); - contact.put("websites", websites); - contact.put("relationships", relationships); + contactId = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID)); + + // If we are in the first row set the oldContactId + if (c.getPosition() == 0) { + oldContactId = contactId; + } + + // When the contact ID changes we need to push the Contact object + // to the array of contacts and create new objects. + if (!oldContactId.equals(contactId)) { + // Populate the Contact object with it's arrays + // and push the contact into the contacts array + contacts.put(populateContact(contact, organizations, addresses, phones, + emails, ims, websites, relationships)); + + // Clean up the objects + contact = new JSONObject(); + organizations = new JSONArray(); + addresses = new JSONArray(); + phones = new JSONArray(); + emails = new JSONArray(); + ims = new JSONArray(); + websites = new JSONArray(); + relationships = new JSONArray(); + + // Set newContact to true as we are starting to populate a new contact + newContact = true; + } + + // When we detect a new contact set the ID and display name. + // These fields are available in every row in the result set returned. + if (newContact) { + newContact = false; + contact.put("id", contactId); + contact.put("displayName", c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))); + } + + // Grab the mimetype of the current row as it will be used in a lot of comparisons + mimetype = c.getString(c.getColumnIndex(ContactsContract.Data.MIMETYPE)); + + if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + && isRequired("name",populate)) { + contact.put("name", nameQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + && isRequired("phoneNumbers",populate)) { + phones.put(phoneQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + && isRequired("emails",populate)) { + emails.put(emailQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + && isRequired("addresses",populate)) { + addresses.put(addressQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + && isRequired("organizations",populate)) { + organizations.put(organizationQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) + && isRequired("ims",populate)) { + ims.put(imQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) + && isRequired("note",populate)) { + contact.put("note",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE))); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + && isRequired("nickname",populate)) { + contact.put("nickname",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME))); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) + && isRequired("urls",populate)) { + websites.put(websiteQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE) + && isRequired("relationships",populate)) { + relationships.put(relationshipQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)) { + if (ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) + && isRequired("anniversary",populate)) { + contact.put("anniversary", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); + } + else if (ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) + && isRequired("birthday",populate)) { + contact.put("birthday", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); + } + } } catch (JSONException e) { - Log.e(LOG_TAG,e.getMessage(),e); + Log.e(LOG_TAG, e.getMessage(),e); } - contacts.put(contact); - pos++; + + // Set the old contact ID + oldContactId = contactId; } + c.close(); + + // Push the last contact into the contacts array + contacts.put(populateContact(contact, organizations, addresses, phones, + emails, ims, websites, relationships)); + + totalEnd = System.currentTimeMillis(); + Log.d(LOG_TAG,"Total time = " + (totalEnd-totalStart)); return contacts; } - private Set buildSetOfContactIds(JSONArray filter, String searchTerm) { - Set contactIds = new HashSet(); + private JSONObject populateContact(JSONObject contact, JSONArray organizations, + JSONArray addresses, JSONArray phones, JSONArray emails, + JSONArray ims, JSONArray websites, JSONArray relationships) { + try { + contact.put("organizations", organizations); + contact.put("addresses", addresses); + contact.put("phoneNumbers", phones); + contact.put("emails", emails); + contact.put("ims", ims); + contact.put("websites", websites); + contact.put("relationships", relationships); + } + catch (JSONException e) { + Log.e(LOG_TAG,e.getMessage(),e); + } + return contact; + } + + private WhereOptions buildWhereClause(JSONArray filter, String searchTerm) { + + ArrayList where = new ArrayList(); + ArrayList whereArgs = new ArrayList(); + WhereOptions options = new WhereOptions(); + /* * Special case for when the user wants all the contacts */ if ("%".equals(searchTerm)) { - doQuery(searchTerm, contactIds, - ContactsContract.Contacts.CONTENT_URI, - ContactsContract.Contacts._ID, - ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?", - new String[] {searchTerm}); - return contactIds; - } - + options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); + options.setWhereArgs(new String[] {searchTerm}); + return options; + } + String key; try { for (int i=0; i contactIds, - Uri uri, String projection, String selection, String[] selectionArgs) { - // Get a cursor by creating the query. - ContentResolver cr = mApp.getContentResolver(); - - Cursor cursor = cr.query( - uri, - new String[] {projection}, - selection, - selectionArgs, - null); - - while (cursor.moveToNext()) { - contactIds.add(cursor.getString(cursor.getColumnIndex(projection))); + // Creating the where string + StringBuffer selection = new StringBuffer(); + for (int i=0; i