From c6ae07ee5c4c91411d97a07015afeac160023c4a Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 11 Sep 2012 18:23:19 -0700 Subject: [PATCH] Turn avatar images in conversations into QuickContactBadge equivalents. This requires a few changes to Recipient in order to make sure we have a Contact URI at click time. While we're at it, let's git rid of the OldRecipientProvider, which was for pre-2.0 contact stuff (no longer supported, woohoo!). --- .../securesms/ConversationItem.java | 22 +++- .../recipients/NewRecipientProvider.java | 85 ++++++------- .../recipients/OldRecipientProvider.java | 94 -------------- .../securesms/recipients/Recipient.java | 44 +++---- .../recipients/RecipientFactory.java | 115 ++++++++---------- .../recipients/RecipientProvider.java | 18 ++- 6 files changed, 132 insertions(+), 246 deletions(-) delete mode 100644 src/org/thoughtcrime/securesms/recipients/OldRecipientProvider.java diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index edf7487697..d260668c31 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -27,6 +27,8 @@ import android.os.Environment; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; +import android.provider.Contacts.Intents; +import android.provider.ContactsContract.QuickContact; import android.telephony.TelephonyManager; import android.text.Spannable; import android.text.format.DateUtils; @@ -249,10 +251,24 @@ public class ConversationItem extends LinearLayout { } private void setBodyImage(MessageRecord messageRecord) { - Recipient recipient = messageRecord.getMessageRecipient(); + final Recipient recipient = messageRecord.getMessageRecipient(); - if (!messageRecord.isOutgoing()) contactPhoto.setImageBitmap(recipient.getContactPhoto()); - else setContactPhotoForUserIdentity(); + if (!messageRecord.isOutgoing()) { + contactPhoto.setImageBitmap(recipient.getContactPhoto()); + contactPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (recipient.getContactUri() != null) { + QuickContact.showQuickContact(context, contactPhoto, recipient.getContactUri(), QuickContact.MODE_LARGE, null); + } else { + Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null)); + context.startActivity(intent); + } + } + }); + } else { + setContactPhotoForUserIdentity(); + } contactPhoto.setVisibility(View.VISIBLE); } diff --git a/src/org/thoughtcrime/securesms/recipients/NewRecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/NewRecipientProvider.java index 91491a05fc..9da563aefb 100644 --- a/src/org/thoughtcrime/securesms/recipients/NewRecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/NewRecipientProvider.java @@ -1,6 +1,6 @@ -/** +/** * Copyright (C) 2011 Whisper Systems - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -10,25 +10,23 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.thoughtcrime.securesms.recipients; -import java.io.InputStream; - - -import android.content.ContentUris; import android.content.Context; -import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.PhoneLookup; +import java.io.InputStream; + public class NewRecipientProvider extends RecipientProvider { private static final String[] CALLER_ID_PROJECTION = new String[] { @@ -36,82 +34,67 @@ public class NewRecipientProvider extends RecipientProvider { PhoneLookup.LOOKUP_KEY, PhoneLookup._ID, }; - + private static final String[] CONTENT_URI_PROJECTION = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, + ContactsContract.Contacts.LOOKUP_KEY }; - + @Override - public Recipient getRecipient(Context context, Uri uri) { + public Recipient getRecipient(Context context, Uri uri) { Cursor cursor = context.getContentResolver().query(uri, CONTENT_URI_PROJECTION, null, null, null); - + try { if (cursor.moveToFirst()) { - long rowId = cursor.getLong(0); - Uri photoLookupUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, rowId); + long rowId = cursor.getLong(0); + Uri contactUri = Contacts.getLookupUri(rowId, cursor.getString(2)); + Bitmap contactPhoto = getContactPhoto(context, contactUri); + String displayName = cursor.getString(1); + cursor.close(); - Bitmap contactPhoto = getContactPhoto(context, photoLookupUri); - String displayName = cursor.getString(1); - cursor.close(); - - cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER}, ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?", new String[] {rowId+""}, null); - if (cursor.moveToFirst()) - return new Recipient(displayName, cursor.getString(0), rowId, contactPhoto); - else - return new Recipient(displayName, null, rowId, contactPhoto); + cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER}, ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?", new String[] {rowId+""}, null); + + if (cursor.moveToFirst()) + return new Recipient(displayName, cursor.getString(0), contactUri, contactPhoto); + else + return new Recipient(displayName, null, contactUri, contactPhoto); } } finally { cursor.close(); } - + return null; } - + @Override public Recipient getRecipient(Context context, String number) { Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION, null, null, null); - + try { if (cursor != null && cursor.moveToFirst()) { - Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, cursor.getLong(2)); - Bitmap contactPhoto = getContactPhoto(context, contactUri); - - Recipient recipient = new Recipient(cursor.getString(0), number, cursor.getLong(2), contactPhoto); - return recipient; + Uri contactUri = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1)); + Bitmap contactPhoto = getContactPhoto(context, contactUri); + + Recipient recipient = new Recipient(cursor.getString(0), number, contactUri, contactPhoto); + return recipient; } } finally { if (cursor != null) - cursor.close(); + cursor.close(); } return null; } - + private Bitmap getContactPhoto(Context context, Uri uri) { InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri); - + if (inputStream == null) return getDefaultContactPhoto(context); - // return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture); else - return BitmapFactory.decodeStream(inputStream); - } - - @Override - public void viewContact(Context context, Recipient recipient) { - Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, recipient.getPersonId()); - Intent intent = new Intent(Intent.ACTION_VIEW, contactUri); - context.startActivity(intent); - } - - @Override - public void addContact(Context context, Recipient recipient) { - Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getNumber()); - context.startActivity(intent); + return BitmapFactory.decodeStream(inputStream); } } diff --git a/src/org/thoughtcrime/securesms/recipients/OldRecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/OldRecipientProvider.java deleted file mode 100644 index 34af4c88ea..0000000000 --- a/src/org/thoughtcrime/securesms/recipients/OldRecipientProvider.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.recipients; - -import org.thoughtcrime.securesms.R; - -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.net.Uri; -import android.provider.Contacts; -import android.provider.Contacts.People; -import android.provider.Contacts.Intents.Insert; -import android.util.Log; - -public class OldRecipientProvider extends RecipientProvider { - - private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" +Contacts.Phones.NUMBER + ",?)"; - @SuppressWarnings("deprecation") - private static final String[] CALLER_ID_PROJECTION = new String[] { - // Contacts.People.Phones.NUMBER, // 0 - // Contacts.People.Phones.LABEL, // 1 - Contacts.People.NAME, // 2 - Contacts.Phones.PERSON_ID, // 3 - Contacts.People.Phones.NUMBER, - }; - - @Override - public Recipient getRecipient(Context context, Uri uri) { - Cursor cursor = context.getContentResolver().query(uri, new String[] {Contacts.People.NAME, Contacts.People._ID, Contacts.People.NUMBER}, null, null, null); - - try { - if (cursor.moveToNext()) { - return new Recipient(cursor.getString(0), cursor.getString(2), cursor.getLong(1), - Contacts.People.loadContactPhoto(context, uri, R.drawable.ic_contact_picture, null)); - } - } finally { - cursor.close(); - } - - return null; - } - - @Override - public Recipient getRecipient(Context context, String number) { - String arguments[] = {number}; - Cursor cursor = context.getContentResolver().query(Contacts.Phones.CONTENT_URI, CALLER_ID_PROJECTION, - CALLER_ID_SELECTION, arguments, null); - try { - if (cursor.moveToFirst()) { - Uri personUri = Uri.withAppendedPath(Contacts.People.CONTENT_URI, cursor.getLong(1)+""); - Recipient recipient = new Recipient(cursor.getString(0), number, cursor.getLong(1), - Contacts.People.loadContactPhoto(context, personUri, R.drawable.ic_contact_picture, null)); - return recipient; - } - } finally { - cursor.close(); - } - - return null; - } - - @Override - public void viewContact(Context context, Recipient recipient) { - Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, recipient.getPersonId()); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - context.startActivity(intent); - } - - @Override - public void addContact(Context context, Recipient recipient) { - Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - intent.setType(Contacts.People.CONTENT_ITEM_TYPE); - intent.putExtra(Insert.PHONE, recipient.getNumber()); - context.startActivity(intent); - } - -} diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index f0568a74fc..88ed853d39 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -1,6 +1,6 @@ -/** +/** * Copyright (C) 2011 Whisper Systems - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -10,13 +10,14 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.thoughtcrime.securesms.recipients; import android.graphics.Bitmap; +import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -31,30 +32,31 @@ public class Recipient implements Parcelable { return new Recipient[size]; } }; - + private final String name; private final String number; - private long personId = -1; + private Uri contactUri; private Bitmap contactPhoto; - - public Recipient(String name, String number, long personId, Bitmap contactPhoto) { + + public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) { this(name, number, contactPhoto); - this.personId = personId; + this.contactUri = contactUri; } - + public Recipient(String name, String number, Bitmap contactPhoto) { this.name = name; this.number = number; this.contactPhoto = contactPhoto; } - + public Recipient(Parcel in) { - this.name = in.readString(); - this.number = in.readString(); + this.name = in.readString(); + this.number = in.readString(); + this.contactUri = in.readParcelable(null); } - - public long getPersonId() { - return personId; + + public Uri getContactUri() { + return this.contactUri; } public String getName() { @@ -64,24 +66,24 @@ public class Recipient implements Parcelable { public String getNumber() { return number; } - + public int describeContents() { - // TODO Auto-generated method stub return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeString(number); + dest.writeParcelable(contactUri, 0); } - + public String toShortString() { return (name == null ? number : name); } - + public Bitmap getContactPhoto() { return contactPhoto; } - - + + } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java index d8eaf1065c..cac6b3f76c 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java @@ -1,6 +1,6 @@ -/** +/** * Copyright (C) 2011 Whisper Systems - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -10,12 +10,22 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.thoughtcrime.securesms.recipients; +import android.content.Context; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.util.NumberUtil; + import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -23,115 +33,86 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.StringTokenizer; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.util.NumberUtil; - -import android.content.Context; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Build; -import android.telephony.PhoneNumberUtils; -import android.util.Log; - public class RecipientFactory { private static final Map recipientCache = Collections.synchronizedMap(new LRUHashMap()); private static final Map recipientIdCache = Collections.synchronizedMap(new LRUHashMap()); - private static final Map recipientUriCache = Collections.synchronizedMap(new HashMap()); - - private static RecipientProvider provider; - - static { - String className; - - int sdkVersion = Integer.parseInt(Build.VERSION.SDK); - - if (sdkVersion <= Build.VERSION_CODES.DONUT) className = "OldRecipientProvider"; - else className = "NewRecipientProvider"; + private static final Map recipientUriCache = Collections.synchronizedMap(new HashMap()); + + private static final RecipientProvider provider = new NewRecipientProvider(); - try { - Class clazz = - Class.forName("org.thoughtcrime.securesms.recipients." + className) - .asSubclass(RecipientProvider.class); - provider = clazz.newInstance(); - } catch (Exception e) { - throw new AssertionError(e); - } - } - public static RecipientProvider getRecipientProvider() { return provider; } - + public static Recipient getRecipientForUri(Context context, Uri uri) { Recipient recipient = recipientUriCache.get(uri); - + if (recipient == null) recipient = getRecipientFromProvider(context, uri); - + return recipient; } - + public static Recipients getRecipientsForIds(Context context, String recipientIds) { ArrayList results = new ArrayList(); StringTokenizer tokenizer = new StringTokenizer(recipientIds.trim(), " "); - + while (tokenizer.hasMoreTokens()) { String recipientId = tokenizer.nextToken(); - + Recipient recipient = recipientIdCache.get(recipientId); - + if (recipient == null) recipient = getRecipientFromProviderId(context, recipientId); - + if (recipient == null) recipient = getNullIdRecipient(context, recipientId); - + results.add(recipient); } - + return new Recipients(results); } - + private static Recipient getRecipientForNumber(Context context, String number) { Recipient recipient = recipientCache.get(number); - + if (recipient == null) recipient = getRecipientFromProvider(context, number); - + if (recipient == null) recipient = getNullRecipient(context, number); - + return recipient; } - + public static Recipients getRecipientsFromString(Context context, String rawText) throws RecipientFormattingException { ArrayList results = new ArrayList(); StringTokenizer tokenizer = new StringTokenizer(rawText, ","); while (tokenizer.hasMoreTokens()) { Recipient recipient = parseRecipient(context, tokenizer.nextToken()); - if( recipient != null ) - results.add(recipient); + if( recipient != null ) + results.add(recipient); } return new Recipients(results); } - + private static Recipient getNullIdRecipient(Context context, String recipientId) { String address = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId); Recipient recipient = getNullRecipient(context, address); recipientIdCache.put(recipientId, recipient); return recipient; } - + private static Recipient getNullRecipient(Context context, String number) { Recipient nullRecipient = new Recipient(null, number, BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture)); recipientCache.put(number, nullRecipient); return nullRecipient; } - + private static Recipient getRecipientFromProviderId(Context context, String recipientId) { Log.w("RecipientFactory", "Hitting recipient provider [ID]."); @@ -144,37 +125,37 @@ public class RecipientFactory { private static Recipient getRecipientFromProvider(Context context, Uri uri) { Recipient recipient = provider.getRecipient(context, uri); - - if (recipient != null) + + if (recipient != null) recipientUriCache.put(uri, recipient); - + return recipient; } private static Recipient getRecipientFromProvider(Context context, String number) { Recipient recipient = provider.getRecipient(context, number); - + if (recipient != null) recipientCache.put(number, recipient); return recipient; } - + private static String parseBracketedNumber(String recipient) throws RecipientFormattingException { int begin = recipient.indexOf('<'); int end = recipient.indexOf('>'); String value = recipient.substring(begin + 1, end); - + if (PhoneNumberUtils.isWellFormedSmsAddress(value)) return value; else throw new RecipientFormattingException("Bracketed value: " + value + " is not valid."); } - + private static Recipient parseRecipient(Context context, String recipient) throws RecipientFormattingException { recipient = recipient.trim(); - if( recipient.length() == 0 ) + if( recipient.length() == 0 ) return null; if ((recipient.indexOf('<') != -1) && (recipient.indexOf('>') != -1)) @@ -182,7 +163,7 @@ public class RecipientFactory { if (NumberUtil.isValidSmsOrEmail(recipient)) return getRecipientForNumber(context, recipient); - + throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted."); } @@ -191,12 +172,12 @@ public class RecipientFactory { recipientIdCache.clear(); recipientUriCache.clear(); } - + private static class LRUHashMap extends LinkedHashMap { private static final int MAX_SIZE = 1000; - @Override + @Override protected boolean removeEldestEntry (Map.Entry eldest) { - return size() > MAX_SIZE; + return size() > MAX_SIZE; } } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java index 3d2aadfb7a..783b947e57 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java @@ -1,6 +1,6 @@ -/** +/** * Copyright (C) 2011 Whisper Systems - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -10,34 +10,32 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.thoughtcrime.securesms.recipients; -import org.thoughtcrime.securesms.R; - import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import org.thoughtcrime.securesms.R; + public abstract class RecipientProvider { - + private static Bitmap defaultContactPhoto; - + public abstract Recipient getRecipient(Context context, String number); public abstract Recipient getRecipient(Context context, Uri uri); - public abstract void viewContact(Context context, Recipient recipient); - public abstract void addContact(Context context, Recipient recipient); public Bitmap getDefaultContactPhoto(Context context) { synchronized (this) { if (defaultContactPhoto == null) defaultContactPhoto = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture); } - + return defaultContactPhoto; } } \ No newline at end of file