From 99398305519f940db70ba392ae884d0a8b8583a2 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 24 Dec 2012 08:40:37 -0800 Subject: [PATCH] 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. --- .../ContactSelectionGroupsFragment.java | 2 +- .../ContactSelectionListFragment.java | 2 +- .../ContactSelectionRecentFragment.java | 2 +- .../securesms/ConversationAdapter.java | 4 +- .../securesms/ConversationItem.java | 17 +- .../securesms/ConversationListActivity.java | 2 +- .../securesms/ConversationListAdapter.java | 14 +- .../securesms/ConversationListItem.java | 50 ++++-- .../securesms/components/RecipientsPanel.java | 2 +- .../securesms/contacts/RecipientsEditor.java | 24 +-- .../securesms/crypto/DecryptingQueue.java | 105 ++++++------ .../database/CanonicalAddressDatabase.java | 35 +++- .../securesms/database/MmsDatabase.java | 2 +- .../securesms/database/SmsDatabase.java | 2 +- .../securesms/database/SmsMigrator.java | 2 +- .../recipients/ContactPhotoFactory.java | 69 ++++++++ .../securesms/recipients/Recipient.java | 108 ++++++++++--- .../recipients/RecipientFactory.java | 120 +++----------- .../recipients/RecipientProvider.java | 153 +++++++++++++----- .../securesms/recipients/Recipients.java | 7 + .../securesms/service/MessageNotifier.java | 4 +- .../securesms/service/MmsSender.java | 2 +- .../securesms/service/SmsReceiver.java | 2 +- .../securesms/service/SmsSender.java | 2 +- .../securesms/util/FutureTaskListener.java | 6 + .../thoughtcrime/securesms/util/LRUCache.java | 18 +++ .../securesms/util/ListenableFutureTask.java | 39 +++++ 27 files changed, 516 insertions(+), 279 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/recipients/ContactPhotoFactory.java create mode 100644 src/org/thoughtcrime/securesms/util/FutureTaskListener.java create mode 100644 src/org/thoughtcrime/securesms/util/LRUCache.java create mode 100644 src/org/thoughtcrime/securesms/util/ListenableFutureTask.java diff --git a/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java index 94be129ac9..88421ba0f3 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java @@ -95,7 +95,7 @@ public class ContactSelectionGroupsFragment extends SherlockListFragment for (ContactData contactData : contactDataList) { for (NumberData numberData : contactData.numbers) { - recipientList.add(new Recipient(contactData.name, numberData.number, null)); + recipientList.add(new Recipient(contactData.name, numberData.number, null, null)); } } } diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java index 2c27a1652b..a999df00cb 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -101,7 +101,7 @@ public class ContactSelectionListFragment extends SherlockListFragment for (ContactData contactData : selectedContacts.values()) { for (NumberData numberData : contactData.numbers) { - recipientList.add(new Recipient(contactData.name, numberData.number, null)); + recipientList.add(new Recipient(contactData.name, numberData.number, null, null)); } } diff --git a/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java index b6df83200e..bd4efc0eae 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java @@ -89,7 +89,7 @@ public class ContactSelectionRecentFragment extends SherlockListFragment for (ContactData contactData : selectedContacts.values()) { for (NumberData numberData : contactData.numbers) { - recipientList.add(new Recipient(contactData.name, numberData.number, null)); + recipientList.add(new Recipient(contactData.name, numberData.number, null, null)); } } diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index b322c4ce16..e0025ddd22 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -266,10 +266,10 @@ public class ConversationAdapter extends CursorAdapter { try { if (address == null) recipient = recipients.getPrimaryRecipient(); - else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient(); + else recipient = RecipientFactory.getRecipientsFromString(context, address, false).getPrimaryRecipient(); } catch (RecipientFormattingException e) { Log.w("ConversationAdapter", e); - recipient = new Recipient("Unknown", "Unknown", null); + recipient = new Recipient("Unknown", "Unknown", null, null); } return recipient; diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 2fb1f22430..266940b019 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -51,8 +51,8 @@ import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.protocol.Tag; +import org.thoughtcrime.securesms.recipients.ContactPhotoFactory; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.service.SendReceiveService; import java.io.File; @@ -283,20 +283,9 @@ public class ConversationItem extends LinearLayout { } } - private void setContactPhotoForUserIdentity() { - Uri selfIdentityContact = ContactIdentityManager.getInstance(context).getSelfIdentityUri(); - - 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)); - } - + Uri uri = ContactIdentityManager.getInstance(context).getSelfIdentityUri(); + contactPhoto.setImageBitmap(ContactPhotoFactory.getLocalUserContactPhoto(context, uri)); contactPhoto.setVisibility(View.VISIBLE); } diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 39d4eeb6e2..1c76c00846 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -329,7 +329,7 @@ public class ConversationListActivity extends SherlockFragmentActivity if (intent.getAction() != null && intent.getAction().equals("android.intent.action.SENDTO")) { Log.w("ConversationListActivity", "Intent has sendto action..."); try { - recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart()); + recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart(), false); thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients); } catch (RecipientFormattingException rfe) { recipients = null; diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index adc881a1f6..ca61004ad5 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -18,6 +18,7 @@ package org.thoughtcrime.securesms; import android.content.Context; import android.database.Cursor; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CursorAdapter; @@ -37,9 +38,10 @@ import java.util.Set; * * @author Moxie Marlinspike */ -public class ConversationListAdapter extends CursorAdapter { +public class ConversationListAdapter extends CursorAdapter { private final Context context; + private final LayoutInflater inflater; private final Set batchSet = Collections.synchronizedSet(new HashSet()); private boolean batchMode = false; @@ -47,21 +49,19 @@ public class ConversationListAdapter extends CursorAdapter { public ConversationListAdapter(Context context, Cursor cursor) { super(context, cursor); this.context = context; + this.inflater = LayoutInflater.from(context); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - ConversationListItem view = new ConversationListItem(context, batchSet); - bindView(view, context, cursor); - - return view; + return inflater.inflate(R.layout.conversation_list_item_view, parent, false); } @Override public void bindView(View view, Context context, Cursor cursor) { long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID)); 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 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); setBody(cursor, thread); - ((ConversationListItem)view).set(thread, batchMode); + ((ConversationListItem)view).set(thread, batchSet, batchMode); } protected void filterBody(ThreadRecord thread, String body) { diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index 4d51688481..692422de1a 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -22,6 +22,7 @@ import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; import android.os.Build; +import android.os.Handler; import android.provider.Contacts.Intents; import android.provider.ContactsContract.QuickContact; import android.text.Spannable; @@ -30,7 +31,6 @@ import android.text.format.DateUtils; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.util.AttributeSet; -import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -52,7 +52,9 @@ import java.util.Set; * @author Moxie Marlinspike */ -public class ConversationListItem extends RelativeLayout { +public class ConversationListItem extends RelativeLayout + implements Recipient.RecipientModifiedListener +{ private Context context; private Set selectedThreads; @@ -62,18 +64,26 @@ public class ConversationListItem extends RelativeLayout { private TextView fromView; private TextView dateView; private CheckBox checkbox; + private long count; + private boolean read; private ImageView contactPhotoImage; private QuickContactBadge contactPhotoBadge; - public ConversationListItem(Context context, Set selectedThreads) { + private final Handler handler = new Handler(); + + public ConversationListItem(Context context) { super(context); + this.context = context; + } - LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - li.inflate(R.layout.conversation_list_item_view, this, true); + public ConversationListItem(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + } - this.context = context; - this.selectedThreads = selectedThreads; + @Override + protected void onFinishInflate() { this.subjectView = (TextView)findViewById(R.id.subject); this.fromView = (TextView)findViewById(R.id.from); this.dateView = (TextView)findViewById(R.id.date); @@ -86,14 +96,14 @@ public class ConversationListItem extends RelativeLayout { intializeListeners(); } - public ConversationListItem(Context context, AttributeSet attrs) { - super(context, attrs); - } + public void set(ThreadRecord thread, Set selectedThreads, boolean batchMode) { + 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.recipients = thread.getRecipients(); - this.threadId = thread.getThreadId(); - this.fromView.setText(formatFrom(recipients, thread.getCount(), thread.isRead())); + this.fromView.setText(formatFrom(recipients, count, read)); if (thread.isKeyExchange()) this.subjectView.setText(R.string.ConversationListItem_key_exchange_message, @@ -114,6 +124,7 @@ public class ConversationListItem extends RelativeLayout { else checkbox.setVisibility(View.GONE); setContactPhoto(this.recipients.getPrimaryRecipient()); + this.recipients.setListener(this); } private void intializeListeners() { @@ -188,4 +199,15 @@ public class ConversationListItem extends RelativeLayout { 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()); + } + }); + } } diff --git a/src/org/thoughtcrime/securesms/components/RecipientsPanel.java b/src/org/thoughtcrime/securesms/components/RecipientsPanel.java index a9f1c49f82..8e7770be6b 100644 --- a/src/org/thoughtcrime/securesms/components/RecipientsPanel.java +++ b/src/org/thoughtcrime/securesms/components/RecipientsPanel.java @@ -81,7 +81,7 @@ public class RecipientsPanel extends RelativeLayout { public Recipients getRecipients() throws RecipientFormattingException { String rawText = recipientsText.getText().toString(); - Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), rawText); + Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), rawText, false); if (recipients.isEmpty()) throw new RecipientFormattingException("Recipient List Is Empty!"); diff --git a/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java b/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java index 3f4d9bc9ab..721db41ef5 100644 --- a/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java +++ b/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java @@ -17,15 +17,6 @@ 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.telephony.PhoneNumberUtils; import android.text.Annotation; @@ -39,11 +30,20 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; -import android.view.MotionEvent; import android.view.ContextMenu.ContextMenuInfo; +import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; 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. */ @@ -52,7 +52,7 @@ public class RecipientsEditor extends MultiAutoCompleteTextView { private final RecipientsEditorTokenizer mTokenizer; private char mLastSeparator = ','; private Context mContext; - + public RecipientsEditor(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.autoCompleteTextViewStyle); mContext = context; @@ -131,7 +131,7 @@ public class RecipientsEditor extends MultiAutoCompleteTextView { public Recipients constructContactsFromInput() { Recipients r = null; try { - r = RecipientFactory.getRecipientsFromString(mContext, mTokenizer.getRawString() ); + r = RecipientFactory.getRecipientsFromString(mContext, mTokenizer.getRawString(), false); } catch (RecipientFormattingException e) { Log.w( "RecipientsEditor", e); } diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index cfd7487bad..e06c761f77 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.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,15 +10,15 @@ * 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.crypto; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; +import android.content.Context; +import android.database.Cursor; +import android.util.Log; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingMmsDatabase; @@ -38,13 +38,14 @@ import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.MultimediaMessagePdu; import ws.com.google.android.mms.pdu.PduParser; -import android.content.Context; -import android.database.Cursor; -import android.util.Log; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; /** * A work queue for processing a number of encryption operations. - * + * * @author Moxie Marlinspike */ @@ -52,12 +53,12 @@ public class DecryptingQueue { private static List workQueue = new LinkedList(); private static Thread workerThread; - + static { workerThread = new WorkerThread(workQueue, "Async Decryption Thread"); workerThread.start(); - } - + } + public static void scheduleDecryption(Context context, MasterSecret masterSecret, long messageId, long threadId, MultimediaMessagePdu mms) { MmsDecryptionItem runnable = new MmsDecryptionItem(context, masterSecret, messageId, threadId, mms); synchronized (workQueue) { @@ -65,7 +66,7 @@ public class DecryptingQueue { workQueue.notifyAll(); } } - + public static void scheduleDecryption(Context context, MasterSecret masterSecret, long messageId, String originator, String body) { DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, body, originator); synchronized (workQueue) { @@ -73,16 +74,16 @@ public class DecryptingQueue { workQueue.notifyAll(); } } - + public static void schedulePendingDecrypts(Context context, MasterSecret masterSecret) { Cursor cursor = null; Log.w("DecryptingQueue", "Processing pending decrypts..."); - + try { cursor = DatabaseFactory.getSmsDatabase(context).getDecryptInProgressMessages(); if (cursor == null || cursor.getCount() == 0 || !cursor.moveToFirst()) return; - + do { scheduleDecryptFromCursor(context, masterSecret, cursor); } while (cursor.moveToNext()); @@ -94,12 +95,12 @@ public class DecryptingQueue { public static void scheduleRogueMessages(Context context, MasterSecret masterSecret, Recipient recipient) { Cursor cursor = null; - + try { cursor = DatabaseFactory.getSmsDatabase(context).getEncryptedRogueMessages(recipient); if (cursor == null || cursor.getCount() == 0 || !cursor.moveToFirst()) return; - + do { DatabaseFactory.getSmsDatabase(context).markAsDecrypting(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); scheduleDecryptFromCursor(context, masterSecret, cursor); @@ -107,9 +108,9 @@ public class DecryptingQueue { } finally { if (cursor != null) cursor.close(); - } + } } - + private static void scheduleDecryptFromCursor(Context context, MasterSecret masterSecret, Cursor cursor) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); String originator = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS)); @@ -117,14 +118,14 @@ public class DecryptingQueue { scheduleDecryption(context, masterSecret, id, originator, body); } - + private static class MmsDecryptionItem implements Runnable { private long messageId; private long threadId; private Context context; private MasterSecret masterSecret; private MultimediaMessagePdu pdu; - + public MmsDecryptionItem(Context context, MasterSecret masterSecret, long messageId, long threadId, MultimediaMessagePdu pdu) { this.context = context; this.masterSecret = masterSecret; @@ -132,7 +133,7 @@ public class DecryptingQueue { this.threadId = threadId; this.pdu = pdu; } - + private byte[] getEncryptedData() { for (int i=0;i addressCache = new HashMap(); - private final Map idCache = Collections.synchronizedMap(new HashMap()); + + private final Map addressCache = Collections.synchronizedMap(new HashMap()); + private final Map idCache = Collections.synchronizedMap(new HashMap()); public static CanonicalAddressDatabase getInstance(Context context) { synchronized (lock) { @@ -58,18 +60,45 @@ public class CanonicalAddressDatabase { private CanonicalAddressDatabase(Context context) { 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) { if (id == null || id.trim().equals("")) return "Anonymous"; String cachedAddress = idCache.get(id); + if (cachedAddress != null) return cachedAddress; Cursor cursor = null; try { + Log.w("CanonicalAddressDatabase", "Hitting DB on query [ID]."); + SQLiteDatabase db = databaseHelper.getReadableDatabase(); 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) { Cursor cursor = null; try { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); + SQLiteDatabase db = databaseHelper.getWritableDatabase(); String[] selectionArguments = new String[] {address}; cursor = db.query(TABLE, ID_PROJECTION, SELECTION, selectionArguments, null, null, null); diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index dfefaababc..0bbedc9c59 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -140,7 +140,7 @@ public class MmsDatabase extends Database { try { EncodedStringValue encodedString = headers.getEncodedStringValue(PduHeaders.FROM); 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); } catch (UnsupportedEncodingException e) { throw new AssertionError(e); diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 64cbf9a8c1..16944b7d4e 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -84,7 +84,7 @@ public class SmsDatabase extends Database { private long insertMessageReceived(SmsMessage message, String body, long type) { List recipientList = new ArrayList(1); - recipientList.add(new Recipient(null, message.getDisplayOriginatingAddress(), null)); + recipientList.add(new Recipient(null, message.getDisplayOriginatingAddress(), null, null)); Recipients recipients = new Recipients(recipientList); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java index cbaa47b45e..51e6536bde 100644 --- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -128,7 +128,7 @@ public class SmsMigrator { try { if (sb.length() == 0) return null; - else return RecipientFactory.getRecipientsFromString(context, sb.toString()); + else return RecipientFactory.getRecipientsFromString(context, sb.toString(), true); } catch (RecipientFormattingException rfe) { Log.w("SmsMigrator", rfe); return null; diff --git a/src/org/thoughtcrime/securesms/recipients/ContactPhotoFactory.java b/src/org/thoughtcrime/securesms/recipients/ContactPhotoFactory.java new file mode 100644 index 0000000000..d579e63af8 --- /dev/null +++ b/src/org/thoughtcrime/securesms/recipients/ContactPhotoFactory.java @@ -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); + } +} diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 63fecf6b5a..ba4f7a53bf 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -21,6 +21,10 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails; + +import java.util.concurrent.atomic.AtomicReference; + public class Recipient implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @@ -33,35 +37,69 @@ public class Recipient implements Parcelable { } }; - private final String name; + private final AtomicReference name = new AtomicReference(null); + private final AtomicReference contactPhoto = new AtomicReference(null); + private final AtomicReference contactUri = new AtomicReference(null); + private final String number; - private Uri contactUri; - private Bitmap contactPhoto; - public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) { - this(name, number, contactPhoto); - this.contactUri = contactUri; - } + private RecipientModifiedListener listener; + private boolean asynchronousUpdateComplete = false; + +// public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) { +// this(name, number, contactPhoto); +// this.contactUri = contactUri; +// } + +// public Recipient(String number, Bitmap contactPhoto, +// ListenableFutureTask future) +// { +// this.number = number; +// this.contactUri = null; +// this.contactPhoto.set(contactPhoto); +// +// future.setListener(new FutureTaskListener() { +// @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) { - this.name = name; - this.number = number; - this.contactPhoto = contactPhoto; + public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) { + this.number = number; + this.contactUri.set(contactUri); + this.name.set(name); + this.contactPhoto.set(contactPhoto); } public Recipient(Parcel in) { - this.name = in.readString(); - this.number = in.readString(); - this.contactUri = in.readParcelable(null); - this.contactPhoto = in.readParcelable(null); + this.number = in.readString(); + + this.name.set(in.readString()); + this.contactUri.set((Uri)in.readParcelable(null)); + this.contactPhoto.set((Bitmap)in.readParcelable(null)); } public Uri getContactUri() { - return this.contactUri; + return this.contactUri.get(); } public String getName() { - return name; + return name.get(); } public String getNumber() { @@ -72,20 +110,44 @@ public class Recipient implements Parcelable { 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) { - dest.writeString(name); dest.writeString(number); - dest.writeParcelable(contactUri, 0); - dest.writeParcelable(contactPhoto, 0); + dest.writeString(name.get()); + dest.writeParcelable(contactUri.get(), 0); + dest.writeParcelable(contactPhoto.get(), 0); } public String toShortString() { - return (name == null ? number : name); + return (name.get() == null ? number : name.get()); } public Bitmap getContactPhoto() { - return contactPhoto; + return contactPhoto.get(); } - + public static interface RecipientModifiedListener { + public void onModified(Recipient recipient); + } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java index 573ec1b9bf..732ff4c60d 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java @@ -17,44 +17,19 @@ package org.thoughtcrime.securesms.recipients; 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.util.NumberUtil; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.StringTokenizer; 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 final RecipientProvider provider = new RecipientProvider(); - 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) { + public static Recipients getRecipientsForIds(Context context, String recipientIds, boolean asynchronous) { if (recipientIds == null || recipientIds.trim().length() == 0) return new Recipients(new LinkedList()); @@ -63,14 +38,7 @@ public class RecipientFactory { 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); + Recipient recipient = getRecipientFromProviderId(context, recipientId, asynchronous); results.add(recipient); } @@ -78,24 +46,18 @@ public class RecipientFactory { 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; + private static Recipient getRecipientForNumber(Context context, String number, boolean asynchronous) { + return provider.getRecipient(context, number, asynchronous); } - public static Recipients getRecipientsFromString(Context context, String rawText) throws RecipientFormattingException { + public static Recipients getRecipientsFromString(Context context, String rawText, boolean asynchronous) + throws RecipientFormattingException + { List results = new LinkedList(); StringTokenizer tokenizer = new StringTokenizer(rawText, ","); while (tokenizer.hasMoreTokens()) { - Recipient recipient = parseRecipient(context, tokenizer.nextToken()); + Recipient recipient = parseRecipient(context, tokenizer.nextToken(), asynchronous); if( recipient != null ) results.add(recipient); } @@ -103,45 +65,9 @@ public class RecipientFactory { 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]."); - - 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 Recipient getRecipientFromProviderId(Context context, String recipientId, boolean asynchronous) { + String number = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId); + return getRecipientForNumber(context, number, asynchronous); } private static boolean hasBracketedNumber(String recipient) { @@ -151,7 +77,9 @@ public class RecipientFactory { (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 end = recipient.indexOf('>', begin); String value = recipient.substring(begin + 1, end); @@ -162,32 +90,26 @@ public class RecipientFactory { 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(); if( recipient.length() == 0 ) return null; if (hasBracketedNumber(recipient)) - return getRecipientForNumber(context, parseBracketedNumber(recipient)); + return getRecipientForNumber(context, parseBracketedNumber(recipient), asynchronous); if (NumberUtil.isValidSmsOrEmail(recipient)) - return getRecipientForNumber(context, recipient); + return getRecipientForNumber(context, recipient, asynchronous); throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted."); } public static void clearCache() { - recipientCache.clear(); - recipientIdCache.clear(); - recipientUriCache.clear(); + ContactPhotoFactory.clearCache(); + provider.clearCache(); } - private static class LRUHashMap extends LinkedHashMap { - private static final int MAX_SIZE = 1000; - @Override - protected boolean removeEldestEntry (Map.Entry eldest) { - return size() > MAX_SIZE; - } - } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java index 0d3d735b31..92cab83cde 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java @@ -21,17 +21,22 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.AsyncTask; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; 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.util.Collections; +import java.util.Map; public class RecipientProvider { - private static Bitmap defaultContactPhoto; + private static final Map recipientCache = Collections.synchronizedMap(new LRUCache(1000)); +// private static final ExecutorService asyncRecipientResolver = Executors.newSingleThreadExecutor(); private static final String[] CALLER_ID_PROJECTION = new String[] { PhoneLookup.DISPLAY_NAME, @@ -39,41 +44,107 @@ public class RecipientProvider { PhoneLookup._ID, }; - private static final String[] CONTENT_URI_PROJECTION = new String[] { - ContactsContract.Contacts._ID, - ContactsContract.Contacts.DISPLAY_NAME, - ContactsContract.Contacts.LOOKUP_KEY - }; + public Recipient getRecipient(Context context, String number, boolean asynchronous) { + Recipient cachedRecipient = recipientCache.get(number); - public Recipient getRecipient(Context context, Uri uri) { - Cursor cursor = context.getContentResolver().query(uri, CONTENT_URI_PROJECTION, null, null, null); + if (cachedRecipient != null) return cachedRecipient; + else if (asynchronous) return getAsynchronousRecipient(context, number); + else return getSynchronousRecipient(context, number); + } - try { - if (cursor.moveToFirst()) { - long rowId = cursor.getLong(0); - Uri contactUri = Contacts.getLookupUri(rowId, cursor.getString(2)); - Bitmap contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI, - rowId+"")); - String displayName = cursor.getString(1); - cursor.close(); + private Recipient getSynchronousRecipient(Context context, String number) { + Log.w("RecipientProvider", "Cache miss [SYNC]!"); + RecipientDetails details = getRecipientDetails(context, number); + Recipient recipient; - 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()) - return new Recipient(displayName, cursor.getString(0), contactUri, contactPhoto); - else - return new Recipient(displayName, null, contactUri, contactPhoto); + recipientCache.put(number, recipient); + return recipient; + } + + 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() { + 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 future = +// new ListenableFutureTask(new Callable() { +// @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)); - 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 { if (cursor != null && cursor.moveToFirst()) { @@ -81,8 +152,7 @@ public class RecipientProvider { Bitmap contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI, cursor.getLong(2)+"")); - Recipient recipient = new Recipient(cursor.getString(0), number, contactUri, contactPhoto); - return recipient; + return new RecipientDetails(cursor.getString(0), contactUri, contactPhoto); } } finally { if (cursor != null) @@ -92,22 +162,25 @@ public class RecipientProvider { 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) { InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri); if (inputStream == null) - return getDefaultContactPhoto(context); + return ContactPhotoFactory.getDefaultContactPhoto(context); else 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; + } + } + } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/recipients/Recipients.java b/src/org/thoughtcrime/securesms/recipients/Recipients.java index 30a1d166b8..52b24e7d4d 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipients.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipients.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import org.thoughtcrime.securesms.crypto.KeyUtil; +import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener; import org.thoughtcrime.securesms.util.NumberUtil; import java.util.ArrayList; @@ -68,6 +69,12 @@ public class Recipients implements Parcelable { return this; } + public void setListener(RecipientModifiedListener listener) { + for (Recipient recipient : recipients) { + recipient.setListener(listener); + } + } + public boolean isEmailRecipient() { for (Recipient recipient : recipients) { if (NumberUtil.isValidEmail(recipient.getNumber())) diff --git a/src/org/thoughtcrime/securesms/service/MessageNotifier.java b/src/org/thoughtcrime/securesms/service/MessageNotifier.java index ca072b4061..27789f4c3c 100644 --- a/src/org/thoughtcrime/securesms/service/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/service/MessageNotifier.java @@ -94,13 +94,13 @@ public class MessageNotifier { private static Recipients getSmsRecipient(Context context, Cursor c) throws RecipientFormattingException { 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 { long messageId = c.getLong(c.getColumnIndexOrThrow(MmsDatabase.ID)); 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) { diff --git a/src/org/thoughtcrime/securesms/service/MmsSender.java b/src/org/thoughtcrime/securesms/service/MmsSender.java index 1046984c7e..0701bd54c7 100644 --- a/src/org/thoughtcrime/securesms/service/MmsSender.java +++ b/src/org/thoughtcrime/securesms/service/MmsSender.java @@ -108,7 +108,7 @@ public class MmsSender extends MmscProcessor { private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) { 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); } } diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index 6c830fa539..fa42881fb6 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -102,7 +102,7 @@ public class SmsReceiver { private void storeKeyExchangeMessage(MasterSecret masterSecret, SmsMessage message, String messageBody) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(ApplicationPreferencesActivity.AUTO_KEY_EXCHANGE_PREF, true)) { try { - Recipient recipient = new Recipient(null, message.getDisplayOriginatingAddress(), null); + Recipient recipient = new Recipient(null, message.getDisplayOriginatingAddress(), null, null); KeyExchangeMessage keyExchangeMessage = new KeyExchangeMessage(messageBody); KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); diff --git a/src/org/thoughtcrime/securesms/service/SmsSender.java b/src/org/thoughtcrime/securesms/service/SmsSender.java index 3ad3e91ed2..e5a7390bcd 100644 --- a/src/org/thoughtcrime/securesms/service/SmsSender.java +++ b/src/org/thoughtcrime/securesms/service/SmsSender.java @@ -175,7 +175,7 @@ public class SmsSender { private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, String address) { 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())); } } diff --git a/src/org/thoughtcrime/securesms/util/FutureTaskListener.java b/src/org/thoughtcrime/securesms/util/FutureTaskListener.java new file mode 100644 index 0000000000..7477fdbc41 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/FutureTaskListener.java @@ -0,0 +1,6 @@ +package org.thoughtcrime.securesms.util; + +public interface FutureTaskListener { + public void onSuccess(V result); + public void onFailure(Throwable error); +} diff --git a/src/org/thoughtcrime/securesms/util/LRUCache.java b/src/org/thoughtcrime/securesms/util/LRUCache.java new file mode 100644 index 0000000000..b89308918d --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/LRUCache.java @@ -0,0 +1,18 @@ +package org.thoughtcrime.securesms.util; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class LRUCache extends LinkedHashMap { + + private final int maxSize; + + public LRUCache(int maxSize) { + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry (Map.Entry eldest) { + return size() > maxSize; + } +} diff --git a/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java b/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java new file mode 100644 index 0000000000..362421b126 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java @@ -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 extends FutureTask { + + private FutureTaskListener listener; + + public ListenableFutureTask(Callable callable, FutureTaskListener listener) { + super(callable); + this.listener = listener; + } + + public synchronized void setListener(FutureTaskListener 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); + } + } + } +}