Refactor recipient access.

1) Refactor recipient class to support asynchronous loading operations.

2) Refactor recipient factory to simplify recipient access.

3) Consoliate everything into one recipient provider that is capable of
doing async lookups and intelligent caching.
pull/1/head
Moxie Marlinspike 12 years ago
parent f685cb550b
commit 9939830551

@ -95,7 +95,7 @@ public class ContactSelectionGroupsFragment extends SherlockListFragment
for (ContactData contactData : contactDataList) { for (ContactData contactData : contactDataList) {
for (NumberData numberData : contactData.numbers) { for (NumberData numberData : contactData.numbers) {
recipientList.add(new Recipient(contactData.name, numberData.number, null)); recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
} }
} }
} }

@ -101,7 +101,7 @@ public class ContactSelectionListFragment extends SherlockListFragment
for (ContactData contactData : selectedContacts.values()) { for (ContactData contactData : selectedContacts.values()) {
for (NumberData numberData : contactData.numbers) { for (NumberData numberData : contactData.numbers) {
recipientList.add(new Recipient(contactData.name, numberData.number, null)); recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
} }
} }

@ -89,7 +89,7 @@ public class ContactSelectionRecentFragment extends SherlockListFragment
for (ContactData contactData : selectedContacts.values()) { for (ContactData contactData : selectedContacts.values()) {
for (NumberData numberData : contactData.numbers) { for (NumberData numberData : contactData.numbers) {
recipientList.add(new Recipient(contactData.name, numberData.number, null)); recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
} }
} }

@ -266,10 +266,10 @@ public class ConversationAdapter extends CursorAdapter {
try { try {
if (address == null) recipient = recipients.getPrimaryRecipient(); if (address == null) recipient = recipients.getPrimaryRecipient();
else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient(); else recipient = RecipientFactory.getRecipientsFromString(context, address, false).getPrimaryRecipient();
} catch (RecipientFormattingException e) { } catch (RecipientFormattingException e) {
Log.w("ConversationAdapter", e); Log.w("ConversationAdapter", e);
recipient = new Recipient("Unknown", "Unknown", null); recipient = new Recipient("Unknown", "Unknown", null, null);
} }
return recipient; return recipient;

@ -51,8 +51,8 @@ import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.protocol.Tag; import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.ContactPhotoFactory;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.service.SendReceiveService;
import java.io.File; import java.io.File;
@ -283,20 +283,9 @@ public class ConversationItem extends LinearLayout {
} }
} }
private void setContactPhotoForUserIdentity() { private void setContactPhotoForUserIdentity() {
Uri selfIdentityContact = ContactIdentityManager.getInstance(context).getSelfIdentityUri(); Uri uri = ContactIdentityManager.getInstance(context).getSelfIdentityUri();
contactPhoto.setImageBitmap(ContactPhotoFactory.getLocalUserContactPhoto(context, uri));
if (selfIdentityContact!= null) {
Recipient recipient = RecipientFactory.getRecipientForUri(context, selfIdentityContact);
if (recipient != null) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
return;
}
} else {
contactPhoto.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_contact_picture));
}
contactPhoto.setVisibility(View.VISIBLE); contactPhoto.setVisibility(View.VISIBLE);
} }

@ -329,7 +329,7 @@ public class ConversationListActivity extends SherlockFragmentActivity
if (intent.getAction() != null && intent.getAction().equals("android.intent.action.SENDTO")) { if (intent.getAction() != null && intent.getAction().equals("android.intent.action.SENDTO")) {
Log.w("ConversationListActivity", "Intent has sendto action..."); Log.w("ConversationListActivity", "Intent has sendto action...");
try { try {
recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart()); recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart(), false);
thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients); thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
} catch (RecipientFormattingException rfe) { } catch (RecipientFormattingException rfe) {
recipients = null; recipients = null;

@ -18,6 +18,7 @@ package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CursorAdapter; import android.widget.CursorAdapter;
@ -37,9 +38,10 @@ import java.util.Set;
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
*/ */
public class ConversationListAdapter extends CursorAdapter { public class ConversationListAdapter extends CursorAdapter {
private final Context context; private final Context context;
private final LayoutInflater inflater;
private final Set<Long> batchSet = Collections.synchronizedSet(new HashSet<Long>()); private final Set<Long> batchSet = Collections.synchronizedSet(new HashSet<Long>());
private boolean batchMode = false; private boolean batchMode = false;
@ -47,21 +49,19 @@ public class ConversationListAdapter extends CursorAdapter {
public ConversationListAdapter(Context context, Cursor cursor) { public ConversationListAdapter(Context context, Cursor cursor) {
super(context, cursor); super(context, cursor);
this.context = context; this.context = context;
this.inflater = LayoutInflater.from(context);
} }
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
ConversationListItem view = new ConversationListItem(context, batchSet); return inflater.inflate(R.layout.conversation_list_item_view, parent, false);
bindView(view, context, cursor);
return view;
} }
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS)); String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientId); Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientId, true);
long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE));
long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
@ -70,7 +70,7 @@ public class ConversationListAdapter extends CursorAdapter {
ThreadRecord thread = new ThreadRecord(context, recipients, date, count, read == 1, threadId); ThreadRecord thread = new ThreadRecord(context, recipients, date, count, read == 1, threadId);
setBody(cursor, thread); setBody(cursor, thread);
((ConversationListItem)view).set(thread, batchMode); ((ConversationListItem)view).set(thread, batchSet, batchMode);
} }
protected void filterBody(ThreadRecord thread, String body) { protected void filterBody(ThreadRecord thread, String body) {

@ -22,6 +22,7 @@ import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.provider.Contacts.Intents; import android.provider.Contacts.Intents;
import android.provider.ContactsContract.QuickContact; import android.provider.ContactsContract.QuickContact;
import android.text.Spannable; import android.text.Spannable;
@ -30,7 +31,6 @@ import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
@ -52,7 +52,9 @@ import java.util.Set;
* @author Moxie Marlinspike * @author Moxie Marlinspike
*/ */
public class ConversationListItem extends RelativeLayout { public class ConversationListItem extends RelativeLayout
implements Recipient.RecipientModifiedListener
{
private Context context; private Context context;
private Set<Long> selectedThreads; private Set<Long> selectedThreads;
@ -62,18 +64,26 @@ public class ConversationListItem extends RelativeLayout {
private TextView fromView; private TextView fromView;
private TextView dateView; private TextView dateView;
private CheckBox checkbox; private CheckBox checkbox;
private long count;
private boolean read;
private ImageView contactPhotoImage; private ImageView contactPhotoImage;
private QuickContactBadge contactPhotoBadge; private QuickContactBadge contactPhotoBadge;
public ConversationListItem(Context context, Set<Long> selectedThreads) { private final Handler handler = new Handler();
public ConversationListItem(Context context) {
super(context); super(context);
this.context = context;
}
LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); public ConversationListItem(Context context, AttributeSet attrs) {
li.inflate(R.layout.conversation_list_item_view, this, true); super(context, attrs);
this.context = context;
}
this.context = context; @Override
this.selectedThreads = selectedThreads; protected void onFinishInflate() {
this.subjectView = (TextView)findViewById(R.id.subject); this.subjectView = (TextView)findViewById(R.id.subject);
this.fromView = (TextView)findViewById(R.id.from); this.fromView = (TextView)findViewById(R.id.from);
this.dateView = (TextView)findViewById(R.id.date); this.dateView = (TextView)findViewById(R.id.date);
@ -86,14 +96,14 @@ public class ConversationListItem extends RelativeLayout {
intializeListeners(); intializeListeners();
} }
public ConversationListItem(Context context, AttributeSet attrs) { public void set(ThreadRecord thread, Set<Long> selectedThreads, boolean batchMode) {
super(context, attrs); this.selectedThreads = selectedThreads;
} this.recipients = thread.getRecipients();
this.threadId = thread.getThreadId();
this.count = thread.getCount();
this.read = thread.isRead();
public void set(ThreadRecord thread, boolean batchMode) { this.fromView.setText(formatFrom(recipients, count, read));
this.recipients = thread.getRecipients();
this.threadId = thread.getThreadId();
this.fromView.setText(formatFrom(recipients, thread.getCount(), thread.isRead()));
if (thread.isKeyExchange()) if (thread.isKeyExchange())
this.subjectView.setText(R.string.ConversationListItem_key_exchange_message, this.subjectView.setText(R.string.ConversationListItem_key_exchange_message,
@ -114,6 +124,7 @@ public class ConversationListItem extends RelativeLayout {
else checkbox.setVisibility(View.GONE); else checkbox.setVisibility(View.GONE);
setContactPhoto(this.recipients.getPrimaryRecipient()); setContactPhoto(this.recipients.getPrimaryRecipient());
this.recipients.setListener(this);
} }
private void intializeListeners() { private void intializeListeners() {
@ -188,4 +199,15 @@ public class ConversationListItem extends RelativeLayout {
else selectedThreads.remove(threadId); else selectedThreads.remove(threadId);
} }
} }
@Override
public void onModified(Recipient recipient) {
handler.post(new Runnable() {
@Override
public void run() {
ConversationListItem.this.fromView.setText(formatFrom(recipients, count, read));
setContactPhoto(ConversationListItem.this.recipients.getPrimaryRecipient());
}
});
}
} }

@ -81,7 +81,7 @@ public class RecipientsPanel extends RelativeLayout {
public Recipients getRecipients() throws RecipientFormattingException { public Recipients getRecipients() throws RecipientFormattingException {
String rawText = recipientsText.getText().toString(); String rawText = recipientsText.getText().toString();
Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), rawText); Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), rawText, false);
if (recipients.isEmpty()) if (recipients.isEmpty())
throw new RecipientFormattingException("Recipient List Is Empty!"); throw new RecipientFormattingException("Recipient List Is Empty!");

@ -17,15 +17,6 @@
package org.thoughtcrime.securesms.contacts; package org.thoughtcrime.securesms.contacts;
import java.util.ArrayList;
import java.util.List;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.recipients.RecipientsFormatter;
import android.content.Context; import android.content.Context;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import android.text.Annotation; import android.text.Annotation;
@ -39,11 +30,20 @@ import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.MultiAutoCompleteTextView; import android.widget.MultiAutoCompleteTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.recipients.RecipientsFormatter;
import java.util.ArrayList;
import java.util.List;
/** /**
* Provide UI for editing the recipients of multi-media messages. * Provide UI for editing the recipients of multi-media messages.
*/ */
@ -131,7 +131,7 @@ public class RecipientsEditor extends MultiAutoCompleteTextView {
public Recipients constructContactsFromInput() { public Recipients constructContactsFromInput() {
Recipients r = null; Recipients r = null;
try { try {
r = RecipientFactory.getRecipientsFromString(mContext, mTokenizer.getRawString() ); r = RecipientFactory.getRecipientsFromString(mContext, mTokenizer.getRawString(), false);
} catch (RecipientFormattingException e) { } catch (RecipientFormattingException e) {
Log.w( "RecipientsEditor", e); Log.w( "RecipientsEditor", e);
} }

@ -16,9 +16,9 @@
*/ */
package org.thoughtcrime.securesms.crypto; package org.thoughtcrime.securesms.crypto;
import java.io.IOException; import android.content.Context;
import java.util.LinkedList; import android.database.Cursor;
import java.util.List; import android.util.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingMmsDatabase; import org.thoughtcrime.securesms.database.EncryptingMmsDatabase;
@ -38,9 +38,10 @@ import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu; import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.PduParser; import ws.com.google.android.mms.pdu.PduParser;
import android.content.Context;
import android.database.Cursor; import java.io.IOException;
import android.util.Log; import java.util.LinkedList;
import java.util.List;
/** /**
* A work queue for processing a number of encryption operations. * A work queue for processing a number of encryption operations.
@ -149,7 +150,7 @@ public class DecryptingQueue {
try { try {
String messageFrom = pdu.getFrom().getString(); String messageFrom = pdu.getFrom().getString();
Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom); Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom, false);
Recipient recipient = recipients.getPrimaryRecipient(); Recipient recipient = recipients.getPrimaryRecipient();
byte[] ciphertextPduBytes = getEncryptedData(); byte[] ciphertextPduBytes = getEncryptedData();
@ -215,7 +216,7 @@ public class DecryptingQueue {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
try { try {
Log.w("DecryptingQueue", "Parsing recipient for originator: " + originator); Log.w("DecryptingQueue", "Parsing recipient for originator: " + originator);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator); Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
Recipient recipient = recipients.getPrimaryRecipient(); Recipient recipient = recipients.getPrimaryRecipient();
Log.w("DecryptingQueue", "Parsed Recipient: " + recipient.getNumber()); Log.w("DecryptingQueue", "Parsed Recipient: " + recipient.getNumber());

@ -22,6 +22,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -44,8 +45,9 @@ public class CanonicalAddressDatabase {
private static CanonicalAddressDatabase instance; private static CanonicalAddressDatabase instance;
private final DatabaseHelper databaseHelper; private final DatabaseHelper databaseHelper;
private final HashMap<String,Long> addressCache = new HashMap<String,Long>();
private final Map<String,String> idCache = Collections.synchronizedMap(new HashMap<String,String>()); private final Map<String,Long> addressCache = Collections.synchronizedMap(new HashMap<String,Long>());
private final Map<String,String> idCache = Collections.synchronizedMap(new HashMap<String,String>());
public static CanonicalAddressDatabase getInstance(Context context) { public static CanonicalAddressDatabase getInstance(Context context) {
synchronized (lock) { synchronized (lock) {
@ -58,18 +60,45 @@ public class CanonicalAddressDatabase {
private CanonicalAddressDatabase(Context context) { private CanonicalAddressDatabase(Context context) {
databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
fillCache();
}
private void fillCache() {
Cursor cursor = null;
try {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
cursor = db.query(TABLE, null, null, null, null, null, null);
while (cursor != null && cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID_COLUMN));
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS_COLUMN));
if (address == null || address.trim().length() == 0)
address = "Anonymous";
idCache.put(id+"", address);
addressCache.put(address, id);
}
} finally {
if (cursor != null)
cursor.close();
}
} }
public String getAddressFromId(String id) { public String getAddressFromId(String id) {
if (id == null || id.trim().equals("")) return "Anonymous"; if (id == null || id.trim().equals("")) return "Anonymous";
String cachedAddress = idCache.get(id); String cachedAddress = idCache.get(id);
if (cachedAddress != null) if (cachedAddress != null)
return cachedAddress; return cachedAddress;
Cursor cursor = null; Cursor cursor = null;
try { try {
Log.w("CanonicalAddressDatabase", "Hitting DB on query [ID].");
SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteDatabase db = databaseHelper.getReadableDatabase();
cursor = db.query(TABLE, null, ID_COLUMN + " = ?", new String[] {id+""}, null, null, null); cursor = db.query(TABLE, null, ID_COLUMN + " = ?", new String[] {id+""}, null, null, null);
@ -127,7 +156,7 @@ public class CanonicalAddressDatabase {
private long getCanonicalAddressFromDatabase(String address) { private long getCanonicalAddressFromDatabase(String address) {
Cursor cursor = null; Cursor cursor = null;
try { try {
SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
String[] selectionArguments = new String[] {address}; String[] selectionArguments = new String[] {address};
cursor = db.query(TABLE, ID_PROJECTION, SELECTION, selectionArguments, null, null, null); cursor = db.query(TABLE, ID_PROJECTION, SELECTION, selectionArguments, null, null, null);

@ -140,7 +140,7 @@ public class MmsDatabase extends Database {
try { try {
EncodedStringValue encodedString = headers.getEncodedStringValue(PduHeaders.FROM); EncodedStringValue encodedString = headers.getEncodedStringValue(PduHeaders.FROM);
String fromString = new String(encodedString.getTextString(), CharacterSets.MIMENAME_ISO_8859_1); String fromString = new String(encodedString.getTextString(), CharacterSets.MIMENAME_ISO_8859_1);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, fromString); Recipients recipients = RecipientFactory.getRecipientsFromString(context, fromString, false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new AssertionError(e); throw new AssertionError(e);

@ -84,7 +84,7 @@ public class SmsDatabase extends Database {
private long insertMessageReceived(SmsMessage message, String body, long type) { private long insertMessageReceived(SmsMessage message, String body, long type) {
List<Recipient> recipientList = new ArrayList<Recipient>(1); List<Recipient> recipientList = new ArrayList<Recipient>(1);
recipientList.add(new Recipient(null, message.getDisplayOriginatingAddress(), null)); recipientList.add(new Recipient(null, message.getDisplayOriginatingAddress(), null, null));
Recipients recipients = new Recipients(recipientList); Recipients recipients = new Recipients(recipientList);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);

@ -128,7 +128,7 @@ public class SmsMigrator {
try { try {
if (sb.length() == 0) return null; if (sb.length() == 0) return null;
else return RecipientFactory.getRecipientsFromString(context, sb.toString()); else return RecipientFactory.getRecipientsFromString(context, sb.toString(), true);
} catch (RecipientFormattingException rfe) { } catch (RecipientFormattingException rfe) {
Log.w("SmsMigrator", rfe); Log.w("SmsMigrator", rfe);
return null; return null;

@ -0,0 +1,69 @@
package org.thoughtcrime.securesms.recipients;
import android.content.Context;
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 org.thoughtcrime.securesms.R;
import java.io.InputStream;
public class ContactPhotoFactory {
private static final Object defaultPhotoLock = new Object();
private static final Object localUserLock = new Object();
private static Bitmap defaultContactPhoto;
private static Bitmap localUserContactPhoto;
private static final String[] CONTENT_URI_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.LOOKUP_KEY
};
public static Bitmap getDefaultContactPhoto(Context context) {
synchronized (defaultPhotoLock) {
if (defaultContactPhoto == null)
defaultContactPhoto = BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_contact_picture);
}
return defaultContactPhoto;
}
public static Bitmap getLocalUserContactPhoto(Context context, Uri uri) {
synchronized (localUserLock) {
if (localUserContactPhoto == null) {
Cursor cursor = context.getContentResolver().query(uri, CONTENT_URI_PROJECTION,
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
localUserContactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI,
cursor.getLong(0) + ""));
} else {
localUserContactPhoto = getDefaultContactPhoto(context);
}
}
}
return localUserContactPhoto;
}
public static void clearCache() {
synchronized (localUserLock) {
localUserContactPhoto = null;
}
}
private static Bitmap getContactPhoto(Context context, Uri uri) {
InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri);
if (inputStream == null) return ContactPhotoFactory.getDefaultContactPhoto(context);
else return BitmapFactory.decodeStream(inputStream);
}
}

@ -21,6 +21,10 @@ import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import java.util.concurrent.atomic.AtomicReference;
public class Recipient implements Parcelable { public class Recipient implements Parcelable {
public static final Parcelable.Creator<Recipient> CREATOR = new Parcelable.Creator<Recipient>() { public static final Parcelable.Creator<Recipient> CREATOR = new Parcelable.Creator<Recipient>() {
@ -33,35 +37,69 @@ public class Recipient implements Parcelable {
} }
}; };
private final String name; private final AtomicReference<String> name = new AtomicReference<String>(null);
private final AtomicReference<Bitmap> contactPhoto = new AtomicReference<Bitmap>(null);
private final AtomicReference<Uri> contactUri = new AtomicReference<Uri>(null);
private final String number; private final String number;
private Uri contactUri;
private Bitmap contactPhoto;
public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) { private RecipientModifiedListener listener;
this(name, number, contactPhoto); private boolean asynchronousUpdateComplete = false;
this.contactUri = contactUri;
} // public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) {
// this(name, number, contactPhoto);
// this.contactUri = contactUri;
// }
// public Recipient(String number, Bitmap contactPhoto,
// ListenableFutureTask<RecipientDetails> future)
// {
// this.number = number;
// this.contactUri = null;
// this.contactPhoto.set(contactPhoto);
//
// future.setListener(new FutureTaskListener<RecipientDetails>() {
// @Override
// public void onSuccess(RecipientDetails result) {
// if (result != null) {
// Recipient.this.name.set(result.name);
// Recipient.this.contactPhoto.set(result.avatar);
//
// synchronized(this) {
// if (listener == null) asynchronousUpdateComplete = true;
// else listener.onModified(Recipient.this);
// }
// }
// }
//
// @Override
// public void onFailure(Throwable error) {
// Log.w("Recipient", error);
// }
// });
// }
public Recipient(String name, String number, Bitmap contactPhoto) { public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) {
this.name = name; this.number = number;
this.number = number; this.contactUri.set(contactUri);
this.contactPhoto = contactPhoto; this.name.set(name);
this.contactPhoto.set(contactPhoto);
} }
public Recipient(Parcel in) { public Recipient(Parcel in) {
this.name = in.readString(); this.number = in.readString();
this.number = in.readString();
this.contactUri = in.readParcelable(null); this.name.set(in.readString());
this.contactPhoto = in.readParcelable(null); this.contactUri.set((Uri)in.readParcelable(null));
this.contactPhoto.set((Bitmap)in.readParcelable(null));
} }
public Uri getContactUri() { public Uri getContactUri() {
return this.contactUri; return this.contactUri.get();
} }
public String getName() { public String getName() {
return name; return name.get();
} }
public String getNumber() { public String getNumber() {
@ -72,20 +110,44 @@ public class Recipient implements Parcelable {
return 0; return 0;
} }
public void updateAsynchronousContent(RecipientDetails result) {
if (result != null) {
Recipient.this.name.set(result.name);
Recipient.this.contactUri.set(result.contactUri);
Recipient.this.contactPhoto.set(result.avatar);
synchronized(this) {
if (listener == null) asynchronousUpdateComplete = true;
else listener.onModified(Recipient.this);
}
}
}
public synchronized void setListener(RecipientModifiedListener listener) {
this.listener = listener;
if (asynchronousUpdateComplete) {
if (listener != null)
listener.onModified(this);
asynchronousUpdateComplete = false;
}
}
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(number); dest.writeString(number);
dest.writeParcelable(contactUri, 0); dest.writeString(name.get());
dest.writeParcelable(contactPhoto, 0); dest.writeParcelable(contactUri.get(), 0);
dest.writeParcelable(contactPhoto.get(), 0);
} }
public String toShortString() { public String toShortString() {
return (name == null ? number : name); return (name.get() == null ? number : name.get());
} }
public Bitmap getContactPhoto() { public Bitmap getContactPhoto() {
return contactPhoto; return contactPhoto.get();
} }
public static interface RecipientModifiedListener {
public void onModified(Recipient recipient);
}
} }

@ -17,44 +17,19 @@
package org.thoughtcrime.securesms.recipients; package org.thoughtcrime.securesms.recipients;
import android.content.Context; import android.content.Context;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
public class RecipientFactory { public class RecipientFactory {
private static final Map<String,Recipient> recipientCache = Collections.synchronizedMap(new LRUHashMap<String,Recipient>());
private static final Map<String,Recipient> recipientIdCache = Collections.synchronizedMap(new LRUHashMap<String,Recipient>());
private static final Map<Uri,Recipient> recipientUriCache = Collections.synchronizedMap(new HashMap<Uri,Recipient>());
private static final RecipientProvider provider = new RecipientProvider(); private static final RecipientProvider provider = new RecipientProvider();
public static RecipientProvider getRecipientProvider() { public static Recipients getRecipientsForIds(Context context, String recipientIds, boolean asynchronous) {
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) {
if (recipientIds == null || recipientIds.trim().length() == 0) if (recipientIds == null || recipientIds.trim().length() == 0)
return new Recipients(new LinkedList<Recipient>()); return new Recipients(new LinkedList<Recipient>());
@ -63,14 +38,7 @@ public class RecipientFactory {
while (tokenizer.hasMoreTokens()) { while (tokenizer.hasMoreTokens()) {
String recipientId = tokenizer.nextToken(); String recipientId = tokenizer.nextToken();
Recipient recipient = getRecipientFromProviderId(context, recipientId, asynchronous);
Recipient recipient = recipientIdCache.get(recipientId);
if (recipient == null)
recipient = getRecipientFromProviderId(context, recipientId);
if (recipient == null)
recipient = getNullIdRecipient(context, recipientId);
results.add(recipient); results.add(recipient);
} }
@ -78,24 +46,18 @@ public class RecipientFactory {
return new Recipients(results); return new Recipients(results);
} }
private static Recipient getRecipientForNumber(Context context, String number) { private static Recipient getRecipientForNumber(Context context, String number, boolean asynchronous) {
Recipient recipient = recipientCache.get(number); return provider.getRecipient(context, number, asynchronous);
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 { public static Recipients getRecipientsFromString(Context context, String rawText, boolean asynchronous)
throws RecipientFormattingException
{
List<Recipient> results = new LinkedList<Recipient>(); List<Recipient> results = new LinkedList<Recipient>();
StringTokenizer tokenizer = new StringTokenizer(rawText, ","); StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
while (tokenizer.hasMoreTokens()) { while (tokenizer.hasMoreTokens()) {
Recipient recipient = parseRecipient(context, tokenizer.nextToken()); Recipient recipient = parseRecipient(context, tokenizer.nextToken(), asynchronous);
if( recipient != null ) if( recipient != null )
results.add(recipient); results.add(recipient);
} }
@ -103,45 +65,9 @@ public class RecipientFactory {
return new Recipients(results); return new Recipients(results);
} }
private static Recipient getNullIdRecipient(Context context, String recipientId) { private static Recipient getRecipientFromProviderId(Context context, String recipientId, boolean asynchronous) {
String address = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId); String number = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId);
Recipient recipient = getNullRecipient(context, address); return getRecipientForNumber(context, number, asynchronous);
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].");
String address = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId);
Recipient recipient = getRecipientFromProvider(context, address);
recipientIdCache.put(recipientId, recipient);
return recipient;
}
private static Recipient getRecipientFromProvider(Context context, Uri uri) {
Recipient recipient = provider.getRecipient(context, uri);
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 boolean hasBracketedNumber(String recipient) { private static boolean hasBracketedNumber(String recipient) {
@ -151,7 +77,9 @@ public class RecipientFactory {
(recipient.indexOf('>', openBracketIndex) != -1); (recipient.indexOf('>', openBracketIndex) != -1);
} }
private static String parseBracketedNumber(String recipient) throws RecipientFormattingException { private static String parseBracketedNumber(String recipient)
throws RecipientFormattingException
{
int begin = recipient.indexOf('<'); int begin = recipient.indexOf('<');
int end = recipient.indexOf('>', begin); int end = recipient.indexOf('>', begin);
String value = recipient.substring(begin + 1, end); String value = recipient.substring(begin + 1, end);
@ -162,32 +90,26 @@ public class RecipientFactory {
throw new RecipientFormattingException("Bracketed value: " + value + " is not valid."); throw new RecipientFormattingException("Bracketed value: " + value + " is not valid.");
} }
private static Recipient parseRecipient(Context context, String recipient) throws RecipientFormattingException { private static Recipient parseRecipient(Context context, String recipient, boolean asynchronous)
throws RecipientFormattingException
{
recipient = recipient.trim(); recipient = recipient.trim();
if( recipient.length() == 0 ) if( recipient.length() == 0 )
return null; return null;
if (hasBracketedNumber(recipient)) if (hasBracketedNumber(recipient))
return getRecipientForNumber(context, parseBracketedNumber(recipient)); return getRecipientForNumber(context, parseBracketedNumber(recipient), asynchronous);
if (NumberUtil.isValidSmsOrEmail(recipient)) if (NumberUtil.isValidSmsOrEmail(recipient))
return getRecipientForNumber(context, recipient); return getRecipientForNumber(context, recipient, asynchronous);
throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted."); throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted.");
} }
public static void clearCache() { public static void clearCache() {
recipientCache.clear(); ContactPhotoFactory.clearCache();
recipientIdCache.clear(); provider.clearCache();
recipientUriCache.clear();
} }
private static class LRUHashMap<K,V> extends LinkedHashMap<K,V> {
private static final int MAX_SIZE = 1000;
@Override
protected boolean removeEldestEntry (Map.Entry<K,V> eldest) {
return size() > MAX_SIZE;
}
}
} }

@ -21,17 +21,22 @@ import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneLookup;
import android.util.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.LRUCache;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
public class RecipientProvider { public class RecipientProvider {
private static Bitmap defaultContactPhoto; private static final Map<String,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<String,Recipient>(1000));
// private static final ExecutorService asyncRecipientResolver = Executors.newSingleThreadExecutor();
private static final String[] CALLER_ID_PROJECTION = new String[] { private static final String[] CALLER_ID_PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME, PhoneLookup.DISPLAY_NAME,
@ -39,41 +44,107 @@ public class RecipientProvider {
PhoneLookup._ID, PhoneLookup._ID,
}; };
private static final String[] CONTENT_URI_PROJECTION = new String[] { public Recipient getRecipient(Context context, String number, boolean asynchronous) {
ContactsContract.Contacts._ID, Recipient cachedRecipient = recipientCache.get(number);
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.LOOKUP_KEY
};
public Recipient getRecipient(Context context, Uri uri) { if (cachedRecipient != null) return cachedRecipient;
Cursor cursor = context.getContentResolver().query(uri, CONTENT_URI_PROJECTION, null, null, null); else if (asynchronous) return getAsynchronousRecipient(context, number);
else return getSynchronousRecipient(context, number);
}
try { private Recipient getSynchronousRecipient(Context context, String number) {
if (cursor.moveToFirst()) { Log.w("RecipientProvider", "Cache miss [SYNC]!");
long rowId = cursor.getLong(0); RecipientDetails details = getRecipientDetails(context, number);
Uri contactUri = Contacts.getLookupUri(rowId, cursor.getString(2)); Recipient recipient;
Bitmap contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI,
rowId+""));
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 (details != null) {
recipient = new Recipient(details.name, number, details.contactUri, details.avatar);
} else {
recipient = new Recipient(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(context));
}
if (cursor.moveToFirst()) recipientCache.put(number, recipient);
return new Recipient(displayName, cursor.getString(0), contactUri, contactPhoto); return recipient;
else }
return new Recipient(displayName, null, contactUri, contactPhoto);
private Recipient getAsynchronousRecipient(final Context context, final String number) {
Log.w("RecipientProvider", "Cache miss [ASYNC]!");
Recipient recipient = new Recipient(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(context));
recipientCache.put(number, recipient);
new AsyncTask<Recipient, Void, RecipientDetails>() {
private Recipient recipient;
@Override
protected RecipientDetails doInBackground(Recipient... recipient) {
this.recipient = recipient[0];
return getRecipientDetails(context, number);
} }
} finally {
cursor.close();
}
return null; @Override
protected void onPostExecute(RecipientDetails result) {
recipient.updateAsynchronousContent(result);
}
}.execute(recipient);
return recipient;
// ListenableFutureTask<RecipientDetails> future =
// new ListenableFutureTask<RecipientDetails>(new Callable<RecipientDetails>() {
// @Override
// public RecipientDetails call() throws Exception {
// return getRecipientDetails(context, number);
//// RecipientDetails recipientDetails = getRecipientDetails();
////
//// if (recipientDeta)
////
//// Recipient cachedRecipient = recipientCache.get(number);
////
//// if (cachedRecipient != null) {
//// return new RecipientDetails(cachedRecipient.getName(), cachedRecipient.getContactPhoto());
//// }
////
//// 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 = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
//// Bitmap contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI,
//// cursor.getLong(2)+""));
////
//// recipientCache.put(number, new Recipient(cursor.getString(0), number, contactPhoto));
//// return new RecipientDetails(cursor.getString(0), contactPhoto);
//// } else {
//// recipientCache.put(number, new Recipient(null, number, ContactPhotoFactory.getDefaultContactPhoto(context)));
//// }
//// } finally {
//// if (cursor != null)
//// cursor.close();
//// }
////
//// return null;
// }
// }, null);
//
// asyncRecipientResolver.submit(future);
// Recipient recipient = new Recipient(number, ContactPhotoFactory.getDefaultContactPhoto(context), future);
// recipientCache.put(number, recipient);
//
// return recipient;
//// return new Recipient(null, number, ContactPhotoFactory.getDefaultContactPhoto(context));
}
public void clearCache() {
recipientCache.clear();
} }
public Recipient getRecipient(Context context, String number) { private RecipientDetails getRecipientDetails(Context context, String number) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION, null, null, null); Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION,
null, null, null);
try { try {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
@ -81,8 +152,7 @@ public class RecipientProvider {
Bitmap contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI, Bitmap contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI,
cursor.getLong(2)+"")); cursor.getLong(2)+""));
Recipient recipient = new Recipient(cursor.getString(0), number, contactUri, contactPhoto); return new RecipientDetails(cursor.getString(0), contactUri, contactPhoto);
return recipient;
} }
} finally { } finally {
if (cursor != null) if (cursor != null)
@ -92,22 +162,25 @@ public class RecipientProvider {
return null; return null;
} }
public Bitmap getDefaultContactPhoto(Context context) {
synchronized (this) {
if (defaultContactPhoto == null)
defaultContactPhoto = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture);
}
return defaultContactPhoto;
}
private Bitmap getContactPhoto(Context context, Uri uri) { private Bitmap getContactPhoto(Context context, Uri uri) {
InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri); InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri);
if (inputStream == null) if (inputStream == null)
return getDefaultContactPhoto(context); return ContactPhotoFactory.getDefaultContactPhoto(context);
else else
return BitmapFactory.decodeStream(inputStream); return BitmapFactory.decodeStream(inputStream);
} }
public static class RecipientDetails {
public final String name;
public final Bitmap avatar;
public final Uri contactUri;
public RecipientDetails(String name, Uri contactUri, Bitmap avatar) {
this.name = name;
this.avatar = avatar;
this.contactUri = contactUri;
}
}
} }

@ -21,6 +21,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.thoughtcrime.securesms.crypto.KeyUtil; import org.thoughtcrime.securesms.crypto.KeyUtil;
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.ArrayList; import java.util.ArrayList;
@ -68,6 +69,12 @@ public class Recipients implements Parcelable {
return this; return this;
} }
public void setListener(RecipientModifiedListener listener) {
for (Recipient recipient : recipients) {
recipient.setListener(listener);
}
}
public boolean isEmailRecipient() { public boolean isEmailRecipient() {
for (Recipient recipient : recipients) { for (Recipient recipient : recipients) {
if (NumberUtil.isValidEmail(recipient.getNumber())) if (NumberUtil.isValidEmail(recipient.getNumber()))

@ -94,13 +94,13 @@ public class MessageNotifier {
private static Recipients getSmsRecipient(Context context, Cursor c) throws RecipientFormattingException { private static Recipients getSmsRecipient(Context context, Cursor c) throws RecipientFormattingException {
String address = c.getString(c.getColumnIndexOrThrow(SmsDatabase.ADDRESS)); String address = c.getString(c.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
return RecipientFactory.getRecipientsFromString(context, address); return RecipientFactory.getRecipientsFromString(context, address, false);
} }
private static Recipients getMmsRecipient(Context context, Cursor c) throws RecipientFormattingException { private static Recipients getMmsRecipient(Context context, Cursor c) throws RecipientFormattingException {
long messageId = c.getLong(c.getColumnIndexOrThrow(MmsDatabase.ID)); long messageId = c.getLong(c.getColumnIndexOrThrow(MmsDatabase.ID));
String address = DatabaseFactory.getMmsDatabase(context).getMessageRecipient(messageId); String address = DatabaseFactory.getMmsDatabase(context).getMessageRecipient(messageId);
return RecipientFactory.getRecipientsFromString(context, address); return RecipientFactory.getRecipientsFromString(context, address, false);
} }
private static Recipients getMostRecentRecipients(Context context, Cursor c) { private static Recipients getMostRecentRecipients(Context context, Cursor c) {

@ -108,7 +108,7 @@ public class MmsSender extends MmscProcessor {
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) { private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
SessionCipher cipher = new SessionCipher(context, masterSecret, new Recipient(null, recipient, null), new TextTransport()); SessionCipher cipher = new SessionCipher(context, masterSecret, new Recipient(null, recipient, null, null), new TextTransport());
return cipher.encryptMessage(pduBytes); return cipher.encryptMessage(pduBytes);
} }
} }

@ -102,7 +102,7 @@ public class SmsReceiver {
private void storeKeyExchangeMessage(MasterSecret masterSecret, SmsMessage message, String messageBody) { private void storeKeyExchangeMessage(MasterSecret masterSecret, SmsMessage message, String messageBody) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(ApplicationPreferencesActivity.AUTO_KEY_EXCHANGE_PREF, true)) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(ApplicationPreferencesActivity.AUTO_KEY_EXCHANGE_PREF, true)) {
try { try {
Recipient recipient = new Recipient(null, message.getDisplayOriginatingAddress(), null); Recipient recipient = new Recipient(null, message.getDisplayOriginatingAddress(), null, null);
KeyExchangeMessage keyExchangeMessage = new KeyExchangeMessage(messageBody); KeyExchangeMessage keyExchangeMessage = new KeyExchangeMessage(messageBody);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);

@ -175,7 +175,7 @@ public class SmsSender {
private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, String address) { private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, String address) {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
SessionCipher cipher = new SessionCipher(context, masterSecret, new Recipient(null, address, null), new SmsTransportDetails()); SessionCipher cipher = new SessionCipher(context, masterSecret, new Recipient(null, address, null, null), new SmsTransportDetails());
return new String(cipher.encryptMessage(body.getBytes())); return new String(cipher.encryptMessage(body.getBytes()));
} }
} }

@ -0,0 +1,6 @@
package org.thoughtcrime.securesms.util;
public interface FutureTaskListener<V> {
public void onSuccess(V result);
public void onFailure(Throwable error);
}

@ -0,0 +1,18 @@
package org.thoughtcrime.securesms.util;
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K,V> extends LinkedHashMap<K,V> {
private final int maxSize;
public LRUCache(int maxSize) {
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry (Map.Entry<K,V> eldest) {
return size() > maxSize;
}
}

@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.util;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ListenableFutureTask<V> extends FutureTask<V> {
private FutureTaskListener<V> listener;
public ListenableFutureTask(Callable<V> callable, FutureTaskListener<V> listener) {
super(callable);
this.listener = listener;
}
public synchronized void setListener(FutureTaskListener<V> listener) {
this.listener = listener;
if (this.isDone()) {
callback();
}
}
@Override
protected synchronized void done() {
callback();
}
private void callback() {
if (this.listener != null) {
try {
this.listener.onSuccess(get());
} catch (ExecutionException ee) {
this.listener.onFailure(ee);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
}
}
Loading…
Cancel
Save