mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-30 05:02:26 -05:00
Remove contacts helper code specific to SDK 7
This commit is contained in:
parent
06ae2a071b
commit
b03b487d62
@ -1,55 +1,76 @@
|
|||||||
package com.fsck.k9.helper;
|
package com.fsck.k9.helper;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.mail.Address;
|
import com.fsck.k9.mail.Address;
|
||||||
|
|
||||||
/**
|
import java.util.ArrayList;
|
||||||
* Helper class to access the contacts stored on the device. This is needed
|
|
||||||
* because the original contacts API introduced with SDK 1 was deprecated with
|
|
||||||
* SDK 5 and will eventually be removed in newer SDK versions.
|
|
||||||
* A class that uses the latest contacts API available on the device will be
|
|
||||||
* loaded at runtime.
|
|
||||||
*
|
|
||||||
* @see ContactsSdk5
|
|
||||||
* @see ContactsSdk5p
|
|
||||||
*/
|
|
||||||
public abstract class Contacts {
|
|
||||||
/**
|
|
||||||
* Instance of the SDK specific class that interfaces with the contacts
|
|
||||||
* API.
|
|
||||||
*/
|
|
||||||
private static Contacts sInstance = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get SDK specific instance of the Contacts class.
|
* Helper class to access the contacts stored on the device.
|
||||||
|
*/
|
||||||
|
public class Contacts {
|
||||||
|
/**
|
||||||
|
* The order in which the search results are returned by
|
||||||
|
* {@link #searchContacts(CharSequence)}.
|
||||||
|
*/
|
||||||
|
protected static final String SORT_ORDER =
|
||||||
|
ContactsContract.CommonDataKinds.Email.TIMES_CONTACTED + " DESC, " +
|
||||||
|
ContactsContract.Contacts.DISPLAY_NAME + ", " +
|
||||||
|
ContactsContract.CommonDataKinds.Email._ID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of columns to load from the database.
|
||||||
|
*
|
||||||
|
* Important: The _ID field is needed by
|
||||||
|
* {@link com.fsck.k9.EmailAddressAdapter} or more specificly by
|
||||||
|
* {@link android.widget.ResourceCursorAdapter}.
|
||||||
|
*/
|
||||||
|
protected static final String PROJECTION[] = {
|
||||||
|
ContactsContract.CommonDataKinds.Email._ID,
|
||||||
|
ContactsContract.Contacts.DISPLAY_NAME,
|
||||||
|
ContactsContract.CommonDataKinds.Email.DATA,
|
||||||
|
ContactsContract.CommonDataKinds.Email.CONTACT_ID
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index of the name field in the projection. This must match the order in
|
||||||
|
* {@link #PROJECTION}.
|
||||||
|
*/
|
||||||
|
protected static final int NAME_INDEX = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index of the email address field in the projection. This must match the
|
||||||
|
* order in {@link #PROJECTION}.
|
||||||
|
*/
|
||||||
|
protected static final int EMAIL_INDEX = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index of the contact id field in the projection. This must match the order in
|
||||||
|
* {@link #PROJECTION}.
|
||||||
|
*/
|
||||||
|
protected static final int CONTACT_ID_INDEX = 3;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance of the Contacts class.
|
||||||
|
*
|
||||||
|
* <p>Note: This is left over from the days when we needed to have SDK-specific code to access
|
||||||
|
* the contacts.</p>
|
||||||
*
|
*
|
||||||
* @param context A {@link Context} instance.
|
* @param context A {@link Context} instance.
|
||||||
* @return Appropriate {@link Contacts} instance for this device.
|
* @return Appropriate {@link Contacts} instance for this device.
|
||||||
*/
|
*/
|
||||||
public static Contacts getInstance(Context context) {
|
public static Contacts getInstance(Context context) {
|
||||||
Context appContext = context.getApplicationContext();
|
return new Contacts(context);
|
||||||
if (sInstance == null) {
|
|
||||||
/*
|
|
||||||
* Check the version of the SDK we are running on. Choose an
|
|
||||||
* implementation class designed for that version of the SDK.
|
|
||||||
*/
|
|
||||||
if (Build.VERSION.SDK_INT <= 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.
|
|
||||||
*/
|
|
||||||
sInstance = new ContactsSdk5p(appContext);
|
|
||||||
} else {
|
|
||||||
sInstance = new ContactsSdk5(appContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sInstance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -76,7 +97,25 @@ public abstract class Contacts {
|
|||||||
* the instance also contains the (display) name of that
|
* the instance also contains the (display) name of that
|
||||||
* entity.
|
* entity.
|
||||||
*/
|
*/
|
||||||
public abstract void createContact(Address email);
|
public void createContact(final Address email) {
|
||||||
|
final Uri contactUri = Uri.fromParts("mailto", email.getAddress(), null);
|
||||||
|
|
||||||
|
final Intent contactIntent = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT);
|
||||||
|
contactIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
contactIntent.setData(contactUri);
|
||||||
|
|
||||||
|
// Pass along full E-mail string for possible create dialog
|
||||||
|
contactIntent.putExtra(ContactsContract.Intents.EXTRA_CREATE_DESCRIPTION,
|
||||||
|
email.toString());
|
||||||
|
|
||||||
|
// Only provide personal name hint if we have one
|
||||||
|
final String senderPersonal = email.getPersonal();
|
||||||
|
if (senderPersonal != null) {
|
||||||
|
contactIntent.putExtra(ContactsContract.Intents.Insert.NAME, senderPersonal);
|
||||||
|
}
|
||||||
|
|
||||||
|
mContext.startActivity(contactIntent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the activity to add a phone number to an existing contact or add a new one.
|
* Start the activity to add a phone number to an existing contact or add a new one.
|
||||||
@ -84,7 +123,13 @@ public abstract class Contacts {
|
|||||||
* @param phoneNumber
|
* @param phoneNumber
|
||||||
* The phone number to add to a contact, or to use when creating a new contact.
|
* The phone number to add to a contact, or to use when creating a new contact.
|
||||||
*/
|
*/
|
||||||
public abstract void addPhoneContact(String phoneNumber);
|
public void addPhoneContact(final String phoneNumber) {
|
||||||
|
Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
|
||||||
|
addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
|
||||||
|
addIntent.putExtra(ContactsContract.Intents.Insert.PHONE, Uri.decode(phoneNumber));
|
||||||
|
addIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
mContext.startActivity(addIntent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the provided email address belongs to one of the contacts.
|
* Check whether the provided email address belongs to one of the contacts.
|
||||||
@ -93,16 +138,52 @@ public abstract class Contacts {
|
|||||||
* @return <tt>true</tt>, if the email address belongs to a contact.
|
* @return <tt>true</tt>, if the email address belongs to a contact.
|
||||||
* <tt>false</tt>, otherwise.
|
* <tt>false</tt>, otherwise.
|
||||||
*/
|
*/
|
||||||
public abstract boolean isInContacts(String emailAddress);
|
public boolean isInContacts(final String emailAddress) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
final Cursor c = getContactByAddress(emailAddress);
|
||||||
|
|
||||||
|
if (c != null) {
|
||||||
|
if (c.getCount() > 0) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the contacts matching the given search term.
|
* Filter the contacts matching the given search term.
|
||||||
*
|
*
|
||||||
* @param filter The search term to filter the contacts.
|
* @param constraint The search term to filter the contacts.
|
||||||
* @return A {@link Cursor} instance that can be used to get the
|
* @return A {@link Cursor} instance that can be used to get the
|
||||||
* matching contacts.
|
* matching contacts.
|
||||||
*/
|
*/
|
||||||
public abstract Cursor searchContacts(CharSequence filter);
|
public Cursor searchContacts(final CharSequence constraint) {
|
||||||
|
final String filter = (constraint == null) ? "" : constraint.toString();
|
||||||
|
final Uri uri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Email.CONTENT_FILTER_URI, Uri.encode(filter));
|
||||||
|
final Cursor c = mContentResolver.query(
|
||||||
|
uri,
|
||||||
|
PROJECTION,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the contact an email address belongs to.
|
* Get the name of the contact an email address belongs to.
|
||||||
@ -111,7 +192,24 @@ public abstract class Contacts {
|
|||||||
* @return The name of the contact the email address belongs to. Or
|
* @return The name of the contact the email address belongs to. Or
|
||||||
* <tt>null</tt> if there's no matching contact.
|
* <tt>null</tt> if there's no matching contact.
|
||||||
*/
|
*/
|
||||||
public abstract String getNameForAddress(String address);
|
public String getNameForAddress(String address) {
|
||||||
|
if (address == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Cursor c = getContactByAddress(address);
|
||||||
|
|
||||||
|
String name = null;
|
||||||
|
if (c != null) {
|
||||||
|
if (c.getCount() > 0) {
|
||||||
|
c.moveToFirst();
|
||||||
|
name = getName(c);
|
||||||
|
}
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the name from a {@link Cursor} instance returned by
|
* Extract the name from a {@link Cursor} instance returned by
|
||||||
@ -120,7 +218,9 @@ public abstract class Contacts {
|
|||||||
* @param cursor The {@link Cursor} instance.
|
* @param cursor The {@link Cursor} instance.
|
||||||
* @return The name of the contact in the {@link Cursor}'s current row.
|
* @return The name of the contact in the {@link Cursor}'s current row.
|
||||||
*/
|
*/
|
||||||
public abstract String getName(Cursor cursor);
|
public String getName(Cursor cursor) {
|
||||||
|
return cursor.getString(NAME_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the email address from a {@link Cursor} instance returned by
|
* Extract the email address from a {@link Cursor} instance returned by
|
||||||
@ -130,7 +230,9 @@ public abstract class Contacts {
|
|||||||
* @return The email address of the contact in the {@link Cursor}'s current
|
* @return The email address of the contact in the {@link Cursor}'s current
|
||||||
* row.
|
* row.
|
||||||
*/
|
*/
|
||||||
public abstract String getEmail(Cursor cursor);
|
public String getEmail(Cursor cursor) {
|
||||||
|
return cursor.getString(EMAIL_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark contacts with the provided email addresses as contacted.
|
* Mark contacts with the provided email addresses as contacted.
|
||||||
@ -138,14 +240,30 @@ public abstract class Contacts {
|
|||||||
* @param addresses Array of {@link Address} objects describing the
|
* @param addresses Array of {@link Address} objects describing the
|
||||||
* contacts to be marked as contacted.
|
* contacts to be marked as contacted.
|
||||||
*/
|
*/
|
||||||
public abstract void markAsContacted(final Address[] addresses);
|
public void markAsContacted(final Address[] addresses) {
|
||||||
|
//TODO: Optimize! Potentially a lot of database queries
|
||||||
|
for (final Address address : addresses) {
|
||||||
|
final Cursor c = getContactByAddress(address.getAddress());
|
||||||
|
|
||||||
|
if (c != null) {
|
||||||
|
if (c.getCount() > 0) {
|
||||||
|
c.moveToFirst();
|
||||||
|
final long personId = c.getLong(CONTACT_ID_INDEX);
|
||||||
|
ContactsContract.Contacts.markAsContacted(mContentResolver, personId);
|
||||||
|
}
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the intent necessary to open a contact picker.
|
* Creates the intent necessary to open a contact picker.
|
||||||
*
|
*
|
||||||
* @return The intent necessary to open a contact picker.
|
* @return The intent necessary to open a contact picker.
|
||||||
*/
|
*/
|
||||||
public abstract Intent contactPickerIntent();
|
public Intent contactPickerIntent() {
|
||||||
|
return new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a contact picker intent, returns a {@code ContactItem} instance for that contact.
|
* Given a contact picker intent, returns a {@code ContactItem} instance for that contact.
|
||||||
@ -156,7 +274,52 @@ public abstract class Contacts {
|
|||||||
* @return A {@link ContactItem} instance describing the picked contact. Or {@code null} if the
|
* @return A {@link ContactItem} instance describing the picked contact. Or {@code null} if the
|
||||||
* contact doesn't have any email addresses.
|
* contact doesn't have any email addresses.
|
||||||
*/
|
*/
|
||||||
public abstract ContactItem extractInfoFromContactPickerIntent(final Intent intent);
|
public ContactItem extractInfoFromContactPickerIntent(final Intent intent) {
|
||||||
|
Cursor cursor = null;
|
||||||
|
ArrayList<String> email = new ArrayList<String>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Uri result = intent.getData();
|
||||||
|
String displayName = null;
|
||||||
|
|
||||||
|
// Get the contact id from the Uri
|
||||||
|
String id = result.getLastPathSegment();
|
||||||
|
|
||||||
|
cursor = mContentResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, PROJECTION,
|
||||||
|
ContactsContract.CommonDataKinds.Email.CONTACT_ID + "=?", new String[] { id }, null);
|
||||||
|
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
String address = cursor.getString(EMAIL_INDEX);
|
||||||
|
if (address != null) {
|
||||||
|
email.add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayName == null) {
|
||||||
|
displayName = cursor.getString(NAME_INDEX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return 'null' if no email addresses have been found
|
||||||
|
if (email.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the first email address found as display name
|
||||||
|
if (displayName == null) {
|
||||||
|
displayName = email.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ContactItem(displayName, email);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(K9.LOG_TAG, "Failed to get email data", e);
|
||||||
|
} finally {
|
||||||
|
Utility.closeQuietly(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get URI to the picture of the contact with the supplied email address.
|
* Get URI to the picture of the contact with the supplied email address.
|
||||||
@ -168,7 +331,47 @@ public abstract class Contacts {
|
|||||||
* @return URI to the picture of the contact with the supplied email address. {@code null} if
|
* @return URI to the picture of the contact with the supplied email address. {@code null} if
|
||||||
* no such contact could be found or the contact doesn't have a picture.
|
* no such contact could be found or the contact doesn't have a picture.
|
||||||
*/
|
*/
|
||||||
public abstract Uri getPhotoUri(String address);
|
public Uri getPhotoUri(String address) {
|
||||||
|
Long contactId;
|
||||||
|
try {
|
||||||
|
final Cursor c = getContactByAddress(address);
|
||||||
|
if (c == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!c.moveToFirst()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
contactId = c.getLong(CONTACT_ID_INDEX);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor cur = mContentResolver.query(
|
||||||
|
ContactsContract.Data.CONTENT_URI,
|
||||||
|
null,
|
||||||
|
ContactsContract.Data.CONTACT_ID + "=" + contactId + " AND "
|
||||||
|
+ ContactsContract.Data.MIMETYPE + "='"
|
||||||
|
+ ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'", null,
|
||||||
|
null);
|
||||||
|
if (cur == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!cur.moveToFirst()) {
|
||||||
|
cur.close();
|
||||||
|
return null; // no photo
|
||||||
|
}
|
||||||
|
// Ok, they have a photo
|
||||||
|
cur.close();
|
||||||
|
Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
|
||||||
|
return Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(K9.LOG_TAG, "Couldn't fetch photo for contact with email " + address, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the device actually have a Contacts application suitable for
|
* Does the device actually have a Contacts application suitable for
|
||||||
@ -184,4 +387,24 @@ public abstract class Contacts {
|
|||||||
}
|
}
|
||||||
return mHasContactPicker;
|
return mHasContactPicker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link Cursor} instance that can be used to fetch information
|
||||||
|
* about the contact with the given email address.
|
||||||
|
*
|
||||||
|
* @param address The email address to search for.
|
||||||
|
* @return A {@link Cursor} instance that can be used to fetch information
|
||||||
|
* about the contact with the given email address
|
||||||
|
*/
|
||||||
|
private Cursor getContactByAddress(final String address) {
|
||||||
|
final Uri uri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI, Uri.encode(address));
|
||||||
|
final Cursor c = mContentResolver.query(
|
||||||
|
uri,
|
||||||
|
PROJECTION,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
SORT_ORDER);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,305 +0,0 @@
|
|||||||
package com.fsck.k9.helper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.provider.ContactsContract;
|
|
||||||
import android.provider.ContactsContract.Contacts;
|
|
||||||
import android.provider.ContactsContract.Intents;
|
|
||||||
import android.provider.ContactsContract.CommonDataKinds.Email;
|
|
||||||
import android.provider.ContactsContract.Intents.Insert;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Address;
|
|
||||||
import com.fsck.k9.K9;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access the contacts on the device using the API introduced with SDK 5.
|
|
||||||
*
|
|
||||||
* @see android.provider.ContactsContract
|
|
||||||
*/
|
|
||||||
public class ContactsSdk5 extends com.fsck.k9.helper.Contacts {
|
|
||||||
/**
|
|
||||||
* The order in which the search results are returned by
|
|
||||||
* {@link #searchContacts(CharSequence)}.
|
|
||||||
*/
|
|
||||||
protected static final String SORT_ORDER =
|
|
||||||
Email.TIMES_CONTACTED + " DESC, " +
|
|
||||||
Contacts.DISPLAY_NAME + ", " +
|
|
||||||
Email._ID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of columns to load from the database.
|
|
||||||
*
|
|
||||||
* Important: The _ID field is needed by
|
|
||||||
* {@link com.fsck.k9.EmailAddressAdapter} or more specificly by
|
|
||||||
* {@link android.widget.ResourceCursorAdapter}.
|
|
||||||
*/
|
|
||||||
protected static final String PROJECTION[] = {
|
|
||||||
Email._ID,
|
|
||||||
Contacts.DISPLAY_NAME,
|
|
||||||
Email.DATA,
|
|
||||||
Email.CONTACT_ID
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Index of the name field in the projection. This must match the order in
|
|
||||||
* {@link #PROJECTION}.
|
|
||||||
*/
|
|
||||||
protected static final int NAME_INDEX = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Index of the email address field in the projection. This must match the
|
|
||||||
* order in {@link #PROJECTION}.
|
|
||||||
*/
|
|
||||||
protected static final int EMAIL_INDEX = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Index of the contact id field in the projection. This must match the order in
|
|
||||||
* {@link #PROJECTION}.
|
|
||||||
*/
|
|
||||||
protected static final int CONTACT_ID_INDEX = 3;
|
|
||||||
|
|
||||||
|
|
||||||
public ContactsSdk5(final Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createContact(final Address email) {
|
|
||||||
final Uri contactUri = Uri.fromParts("mailto", email.getAddress(), null);
|
|
||||||
|
|
||||||
final Intent contactIntent = new Intent(Intents.SHOW_OR_CREATE_CONTACT);
|
|
||||||
contactIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
contactIntent.setData(contactUri);
|
|
||||||
|
|
||||||
// Pass along full E-mail string for possible create dialog
|
|
||||||
contactIntent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION,
|
|
||||||
email.toString());
|
|
||||||
|
|
||||||
// Only provide personal name hint if we have one
|
|
||||||
final String senderPersonal = email.getPersonal();
|
|
||||||
if (senderPersonal != null) {
|
|
||||||
contactIntent.putExtra(Intents.Insert.NAME, senderPersonal);
|
|
||||||
}
|
|
||||||
|
|
||||||
mContext.startActivity(contactIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addPhoneContact(final String phoneNumber) {
|
|
||||||
Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
|
|
||||||
addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
|
|
||||||
addIntent.putExtra(Insert.PHONE, Uri.decode(phoneNumber));
|
|
||||||
addIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
mContext.startActivity(addIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isInContacts(final String emailAddress) {
|
|
||||||
boolean result = false;
|
|
||||||
|
|
||||||
final Cursor c = getContactByAddress(emailAddress);
|
|
||||||
|
|
||||||
if (c != null) {
|
|
||||||
if (c.getCount() > 0) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor searchContacts(final CharSequence constraint) {
|
|
||||||
final String filter = (constraint == null) ? "" : constraint.toString();
|
|
||||||
final Uri uri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, Uri.encode(filter));
|
|
||||||
final Cursor c = mContentResolver.query(
|
|
||||||
uri,
|
|
||||||
PROJECTION,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getNameForAddress(String address) {
|
|
||||||
if (address == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Cursor c = getContactByAddress(address);
|
|
||||||
|
|
||||||
String name = null;
|
|
||||||
if (c != null) {
|
|
||||||
if (c.getCount() > 0) {
|
|
||||||
c.moveToFirst();
|
|
||||||
name = getName(c);
|
|
||||||
}
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName(Cursor c) {
|
|
||||||
return c.getString(NAME_INDEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getEmail(Cursor c) {
|
|
||||||
return c.getString(EMAIL_INDEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void markAsContacted(final Address[] addresses) {
|
|
||||||
//TODO: Optimize! Potentially a lot of database queries
|
|
||||||
for (final Address address : addresses) {
|
|
||||||
final Cursor c = getContactByAddress(address.getAddress());
|
|
||||||
|
|
||||||
if (c != null) {
|
|
||||||
if (c.getCount() > 0) {
|
|
||||||
c.moveToFirst();
|
|
||||||
final long personId = c.getLong(CONTACT_ID_INDEX);
|
|
||||||
ContactsContract.Contacts.markAsContacted(mContentResolver, personId);
|
|
||||||
}
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent contactPickerIntent() {
|
|
||||||
return new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContactItem extractInfoFromContactPickerIntent(final Intent data) {
|
|
||||||
Cursor cursor = null;
|
|
||||||
ArrayList<String> email = new ArrayList<String>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Uri result = data.getData();
|
|
||||||
String displayName = null;
|
|
||||||
|
|
||||||
// Get the contact id from the Uri
|
|
||||||
String id = result.getLastPathSegment();
|
|
||||||
|
|
||||||
cursor = mContentResolver.query(Email.CONTENT_URI, PROJECTION,
|
|
||||||
Email.CONTACT_ID + "=?", new String[] { id }, null);
|
|
||||||
|
|
||||||
if (cursor != null) {
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
String address = cursor.getString(EMAIL_INDEX);
|
|
||||||
if (address != null) {
|
|
||||||
email.add(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayName == null) {
|
|
||||||
displayName = cursor.getString(NAME_INDEX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return 'null' if no email addresses have been found
|
|
||||||
if (email.size() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the first email address found as display name
|
|
||||||
if (displayName == null) {
|
|
||||||
displayName = email.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ContactItem(displayName, email);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(K9.LOG_TAG, "Failed to get email data", e);
|
|
||||||
} finally {
|
|
||||||
Utility.closeQuietly(cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a {@link Cursor} instance that can be used to fetch information
|
|
||||||
* about the contact with the given email address.
|
|
||||||
*
|
|
||||||
* @param address The email address to search for.
|
|
||||||
* @return A {@link Cursor} instance that can be used to fetch information
|
|
||||||
* about the contact with the given email address
|
|
||||||
*/
|
|
||||||
private Cursor getContactByAddress(final String address) {
|
|
||||||
final Uri uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(address));
|
|
||||||
final Cursor c = mContentResolver.query(
|
|
||||||
uri,
|
|
||||||
PROJECTION,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
SORT_ORDER);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getPhotoUri(String address) {
|
|
||||||
Long contactId;
|
|
||||||
try {
|
|
||||||
final Cursor c = getContactByAddress(address);
|
|
||||||
if (c == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!c.moveToFirst()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
contactId = c.getLong(CONTACT_ID_INDEX);
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor cur = mContentResolver.query(
|
|
||||||
ContactsContract.Data.CONTENT_URI,
|
|
||||||
null,
|
|
||||||
ContactsContract.Data.CONTACT_ID + "=" + contactId + " AND "
|
|
||||||
+ ContactsContract.Data.MIMETYPE + "='"
|
|
||||||
+ ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'", null,
|
|
||||||
null);
|
|
||||||
if (cur == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!cur.moveToFirst()) {
|
|
||||||
cur.close();
|
|
||||||
return null; // no photo
|
|
||||||
}
|
|
||||||
// Ok, they have a photo
|
|
||||||
cur.close();
|
|
||||||
Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
|
|
||||||
return Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(K9.LOG_TAG, "Couldn't fetch photo for contact with email " + address, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user