From 737edf369b3b75eb11914d599c52a0597aac78ea Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 25 Jan 2011 02:34:02 +0000 Subject: [PATCH] Use new contacts API on Android 2.0, 2.0.1, and 2.1 devices with additional code to match by phonetic names. Fixes issue 2906 --- src/com/fsck/k9/helper/Contacts.java | 14 ++- src/com/fsck/k9/helper/ContactsSdk5.java | 10 +- src/com/fsck/k9/helper/ContactsSdk5p.java | 115 ++++++++++++++++++++++ 3 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 src/com/fsck/k9/helper/ContactsSdk5p.java diff --git a/src/com/fsck/k9/helper/Contacts.java b/src/com/fsck/k9/helper/Contacts.java index 3bc5d7e02..31f5f53d9 100644 --- a/src/com/fsck/k9/helper/Contacts.java +++ b/src/com/fsck/k9/helper/Contacts.java @@ -45,14 +45,18 @@ public abstract class Contacts int sdkVersion = Integer.parseInt(Build.VERSION.SDK); String className = null; - /* - * The new API appeared in Eclair, but it supported phonetic names in Froyo. - * Therefore we employ the old API in Eclair. - */ - if (sdkVersion <= Build.VERSION_CODES.ECLAIR_MR1) + if (sdkVersion <= Build.VERSION_CODES.DONUT) { className = "com.fsck.k9.helper.ContactsSdk3_4"; } + else if (sdkVersion <= Build.VERSION_CODES.ECLAIR_MR1) + { + /* + * The new API was introduced with SDK 5. But Android versions < 2.2 + * need some additional code to be able to search for phonetic names. + */ + className = "com.fsck.k9.helper.ContactsSdk5p"; + } else { className = "com.fsck.k9.helper.ContactsSdk5"; diff --git a/src/com/fsck/k9/helper/ContactsSdk5.java b/src/com/fsck/k9/helper/ContactsSdk5.java index afd8d111c..ce4085cc1 100644 --- a/src/com/fsck/k9/helper/ContactsSdk5.java +++ b/src/com/fsck/k9/helper/ContactsSdk5.java @@ -23,7 +23,7 @@ public class ContactsSdk5 extends com.fsck.k9.helper.Contacts * The order in which the search results are returned by * {@link #searchContacts(CharSequence)}. */ - private static final String SORT_ORDER = + protected static final String SORT_ORDER = Email.TIMES_CONTACTED + " DESC, " + Contacts.DISPLAY_NAME + ", " + Email._ID; @@ -35,7 +35,7 @@ public class ContactsSdk5 extends com.fsck.k9.helper.Contacts * {@link com.fsck.k9.EmailAddressAdapter} or more specificly by * {@link android.widget.ResourceCursorAdapter}. */ - private static final String PROJECTION[] = + protected static final String PROJECTION[] = { Email._ID, Contacts.DISPLAY_NAME, @@ -47,19 +47,19 @@ public class ContactsSdk5 extends com.fsck.k9.helper.Contacts * Index of the name field in the projection. This must match the order in * {@link #PROJECTION}. */ - private static final int NAME_INDEX = 1; + protected static final int NAME_INDEX = 1; /** * Index of the email address field in the projection. This must match the * order in {@link #PROJECTION}. */ - private static final int EMAIL_INDEX = 2; + protected static final int EMAIL_INDEX = 2; /** * Index of the contact id field in the projection. This must match the order in * {@link #PROJECTION}. */ - private static final int CONTACT_ID_INDEX = 3; + protected static final int CONTACT_ID_INDEX = 3; public ContactsSdk5(final Context context) diff --git a/src/com/fsck/k9/helper/ContactsSdk5p.java b/src/com/fsck/k9/helper/ContactsSdk5p.java new file mode 100644 index 000000000..46fc2a42b --- /dev/null +++ b/src/com/fsck/k9/helper/ContactsSdk5p.java @@ -0,0 +1,115 @@ +package com.fsck.k9.helper; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; + +/** + * Access the contacts on the device using the API introduced with SDK 5. + * Use some additional code to make search work with phonetic names. + * + * Android versions >= 2.2 (Froyo) support searching for phonetic names + * out of the box (see {@link ContactsSdk5}). + * + * @see android.provider.ContactsContract + */ +public class ContactsSdk5p extends ContactsSdk5 +{ + public ContactsSdk5p(final Context context) + { + super(context); + } + + @Override + public Cursor searchContacts(final CharSequence constraint) + { + if (constraint == null) + { + return null; + } + + // Lookup using Email.CONTENT_FILTER_URI to get matching contact ids. + // This does all sorts of magic we don't want to replicate. + final String filter = constraint.toString(); + final Uri uri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, Uri.encode(filter)); + final Cursor cursor = mContentResolver.query( + uri, + new String[] {Email.CONTACT_ID}, + null, + null, + null); + + final StringBuilder matches = new StringBuilder(); + if ((cursor != null) && (cursor.getCount() > 0)) + { + boolean first = true; + while (cursor.moveToNext()) + { + if (first) + { + first = false; + } + else + { + matches.append(","); + } + matches.append(cursor.getLong(0)); + } + cursor.close(); + } + + // Find contacts with email addresses that have been found using + // Email.CONTENT_FILTER_URI above or ones that have a matching phonetic name. + final String where = Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'" + + " AND " + + "(" + + // Match if found by Email.CONTENT_FILTER_URI + Email.CONTACT_ID + " IN (" + matches.toString() + ")" + + " OR " + + // Match if phonetic given name starts with "constraint" + StructuredName.PHONETIC_GIVEN_NAME + " LIKE ?" + + " OR " + + // Match if phonetic given name contains a word that starts with "constraint" + StructuredName.PHONETIC_GIVEN_NAME + " LIKE ?" + + " OR " + + // Match if phonetic middle name starts with "constraint" + StructuredName.PHONETIC_MIDDLE_NAME + " LIKE ?" + + " OR " + + // Match if phonetic middle name contains a word that starts with "constraint" + StructuredName.PHONETIC_MIDDLE_NAME + " LIKE ?" + + " OR " + + // Match if phonetic family name starts with "constraint" + StructuredName.PHONETIC_FAMILY_NAME + " LIKE ?" + + " OR " + + // Match if phonetic family name contains a word that starts with "constraint" + StructuredName.PHONETIC_FAMILY_NAME + " LIKE ?" + + ")"; + final String filter1 = constraint.toString() + "%"; + final String filter2 = "% " + filter1; + final String[] args = new String[] {filter1, filter2, filter1, filter2, filter1, filter2}; + final Cursor c = mContentResolver.query( + Email.CONTENT_URI, + PROJECTION, + where, + args, + SORT_ORDER); + + if (c != null) + { + /* + * To prevent expensive execution in the UI thread: + * Cursors get lazily executed, so if you don't call anything on + * the cursor before returning it from the background thread you'll + * have a complied program for the cursor, but it won't have been + * executed to generate the data yet. Often the execution is more + * expensive than the compilation... + */ + c.getCount(); + } + + return c; + } +}