Conversation.java revision 6cba8248f3e41921b03cc74a823a6347016e69ba
1f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereirapackage com.android.mms.data; 2f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 3f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport java.util.HashSet; 4f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport java.util.Iterator; 5f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport java.util.Set; 6f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 7f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.content.AsyncQueryHandler; 8f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.content.ContentUris; 9f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.content.ContentValues; 10f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.content.Context; 11f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.database.Cursor; 12f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.net.Uri; 13f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.provider.Telephony.MmsSms; 14f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.provider.Telephony.Threads; 15f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.provider.Telephony.Sms.Conversations; 16f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.text.TextUtils; 17f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.util.Log; 18f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 19f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport com.android.mms.R; 20b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huangimport com.android.mms.transaction.MessagingNotification; 21f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport com.android.mms.ui.MessageUtils; 22f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereiraimport com.android.mms.util.DraftCache; 237517e3b61b898a57f19be0671f70d58a82224643Andy Huang 248db7e407109532557718c6b8064792f2df7a073dMindy Pereira/** 25f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * An interface for finding information about conversations and/or creating new ones. 26f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira */ 27f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereirapublic class Conversation { 28f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira private static final String TAG = "Conversation"; 297c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein private static final boolean DEBUG = false; 307c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein 31af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao private static final Uri sAllThreadsUri = 32f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); 33f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 34f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira private static final String[] ALL_THREADS_PROJECTION = { 35042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang Threads._ID, Threads.DATE, Threads.MESSAGE_COUNT, Threads.RECIPIENT_IDS, 36bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.ERROR, 37f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira Threads.HAS_ATTACHMENT 381ef988f0c8be136fda75ed207c222413db1d3f0cMindy Pereira }; 396126d72ae2769bd39451872f45781cadb5b90515Mark Wei private static final int ID = 0; 40f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira private static final int DATE = 1; 4106642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira private static final int MESSAGE_COUNT = 2; 42b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huang private static final int RECIPIENT_IDS = 3; 43f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira private static final int SNIPPET = 4; 44b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook private static final int SNIPPET_CS = 5; 45b0219eb7b77212902a38821c050922ac73a68250Mindy Pereira private static final int READ = 6; 467517e3b61b898a57f19be0671f70d58a82224643Andy Huang private static final int ERROR = 7; 47f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira private static final int HAS_ATTACHMENT = 8; 48866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira 49f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 508db7e407109532557718c6b8064792f2df7a073dMindy Pereira private final Context mContext; 51f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 5269b440141f402669fe44dfd8924a94fd22ebccf7mindyp // The thread ID of this conversation. Can be zero in the case of a 536126d72ae2769bd39451872f45781cadb5b90515Mark Wei // new conversation where the recipient set is changing as the user 54ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira // types and we have not hit the database yet to create a thread. 55489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang private long mThreadId; 56b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook 5752a61db87c487fe5bb7cc673037887a6d35b0f0fMark Wei private ContactList mRecipients; // The current set of recipients. 58866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira private long mDate; // The last update time. 596c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira private int mMessageCount; // Number of messages. 606126d72ae2769bd39451872f45781cadb5b90515Mark Wei private String mSnippet; // Text of the most recent message. 6106642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira private boolean mHasUnreadMessages; // True if there are unread messages. 629365a826b46b0e274df88e92534f7d871eef2aa2mindyp private boolean mHasAttachment; // True if any message has an attachment. 6369b440141f402669fe44dfd8924a94fd22ebccf7mindyp private boolean mHasError; // True if any message is in an error state. 64866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira 65e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang private static ContentValues mReadContentValues; 66e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang 674485ade4b8c949a222f8b98650a9a48d074dc87eVikram Aggarwal 684485ade4b8c949a222f8b98650a9a48d074dc87eVikram Aggarwal private Conversation(Context context) { 69f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mContext = context; 70f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mRecipients = new ContactList(); 71f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mThreadId = 0; 72f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 73f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 74f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira private Conversation(Context context, long threadId) { 75f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mContext = context; 76f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira if (!loadFromThreadId(threadId)) { 77f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mRecipients = new ContactList(); 78f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mThreadId = 0; 792229eddfba6f7517c43ef49ee67c50c24d4b961aMark Wei } 80f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 81f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira 826f0b2be9d3855e93ec2977b2736798c60adb9b7eMindy Pereira private Conversation(Context context, Cursor cursor, boolean allowQuery) { 836f0b2be9d3855e93ec2977b2736798c60adb9b7eMindy Pereira mContext = context; 84f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira fillFromCursor(context, this, cursor, allowQuery); 85f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 86f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira 87f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira /** 88f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira * Create a new conversation with no recipients. {@link setRecipients} can 89f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira * be called as many times as you like; the conversation will not be 90f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira * created in the database until {@link ensureThreadId} is called. 91f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira */ 92f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira public static Conversation createNew(Context context) { 93f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira return new Conversation(context); 94f913894d3f1fa80b7174477999074b813e1deb12Mindy Pereira } 957517e3b61b898a57f19be0671f70d58a82224643Andy Huang 967517e3b61b898a57f19be0671f70d58a82224643Andy Huang /** 97bc88f16af48d9538b79cbeab88cd275cb769a67dVikram Aggarwal * Find the conversation matching the provided thread ID. 987517e3b61b898a57f19be0671f70d58a82224643Andy Huang */ 997517e3b61b898a57f19be0671f70d58a82224643Andy Huang public static Conversation get(Context context, long threadId) { 1007517e3b61b898a57f19be0671f70d58a82224643Andy Huang synchronized (Cache.getInstance()) { 101bc88f16af48d9538b79cbeab88cd275cb769a67dVikram Aggarwal Conversation conv = Cache.get(threadId); 1027517e3b61b898a57f19be0671f70d58a82224643Andy Huang if (conv != null) 1037517e3b61b898a57f19be0671f70d58a82224643Andy Huang return conv; 1047517e3b61b898a57f19be0671f70d58a82224643Andy Huang 1057517e3b61b898a57f19be0671f70d58a82224643Andy Huang conv = new Conversation(context, threadId); 106ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira try { 107ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira Cache.put(conv); 108ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira } catch (IllegalStateException e) { 109ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira Log.e(TAG, "Tried to add duplicate Conversation to Cache"); 110ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira } 111ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira return conv; 112ff9aff3c28c4c20c7866ebfaaa642997e324f274Mindy Pereira } 1136c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira } 1146c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira 115f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira /** 116f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * Find the conversation matching the provided recipient set. 1177c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein * When called with an empty recipient list, equivalent to {@link createEmpty}. 1189365a826b46b0e274df88e92534f7d871eef2aa2mindyp */ 1199365a826b46b0e274df88e92534f7d871eef2aa2mindyp public static Conversation get(Context context, ContactList recipients) { 1209365a826b46b0e274df88e92534f7d871eef2aa2mindyp // If there are no recipients in the list, make a new conversation. 121de3e74a82043733243c7391d7f983a5af8842891Mindy Pereira if (recipients.size() < 1) { 122de3e74a82043733243c7391d7f983a5af8842891Mindy Pereira return createNew(context); 123de3e74a82043733243c7391d7f983a5af8842891Mindy Pereira } 124de3e74a82043733243c7391d7f983a5af8842891Mindy Pereira 125866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira synchronized (Cache.getInstance()) { 126866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira Conversation conv = Cache.get(recipients); 127866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira if (conv != null) 128866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira return conv; 1296126d72ae2769bd39451872f45781cadb5b90515Mark Wei 1306126d72ae2769bd39451872f45781cadb5b90515Mark Wei long threadId = getOrCreateThreadId(context, recipients); 1316126d72ae2769bd39451872f45781cadb5b90515Mark Wei conv = new Conversation(context, threadId); 1326126d72ae2769bd39451872f45781cadb5b90515Mark Wei 13306642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira Cache.put(conv); 13406642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira return conv; 13506642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira } 13606642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira } 137866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira 138866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira /** 139866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira * Find the conversation matching in the specified Uri. Example 140866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira * forms: {@value content://mms-sms/conversations/3} or 141866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira * {@value sms:+12124797990}. 142f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * When called with a null Uri, equivalent to {@link createEmpty}. 143f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira */ 1443432b636fbcf7d6cc551b9705ed4dd88ae2b4f49Jin Cao public static Conversation get(Context context, Uri uri) { 14519bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira if (uri == null) { 14669b440141f402669fe44dfd8924a94fd22ebccf7mindyp return createNew(context); 14769b440141f402669fe44dfd8924a94fd22ebccf7mindyp } 14819bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira 149f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira if (DEBUG) { 150f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira Log.v(TAG, "Conversation get URI: " + uri); 151f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 152f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira // Handle a conversation URI 1533432b636fbcf7d6cc551b9705ed4dd88ae2b4f49Jin Cao if (uri.getPathSegments().size() >= 2) { 154f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira try { 155f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira long threadId = Long.parseLong(uri.getPathSegments().get(1)); 156f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira if (DEBUG) { 157f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira Log.v(TAG, "Conversation get threadId: " + threadId); 158f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 159f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira return get(context, threadId); 160f69d0394f7de5c0681b8d5d5ca0c60b55c55f815Vikram Aggarwal } catch (NumberFormatException exception) { 161f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira Log.e(TAG, "Invalid URI: " + uri); 162f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 163f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 164f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 165f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira String recipient = uri.getSchemeSpecificPart(); 166f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira return get(context, ContactList.getByNumbers(recipient, false)); 167f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 168866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira 1691ef988f0c8be136fda75ed207c222413db1d3f0cMindy Pereira /** 1701ef988f0c8be136fda75ed207c222413db1d3f0cMindy Pereira * Returns a temporary Conversation (not representing one on disk) wrapping 1711ef988f0c8be136fda75ed207c222413db1d3f0cMindy Pereira * the contents of the provided cursor. The cursor should be the one 172866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira * returned to your AsyncQueryHandler passed in to {@link startQueryForAll}. 173866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira * The recipient list of this conversation can be empty if the results 174f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * were not in cache. 175f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira */ 176f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira public static Conversation from(Context context, Cursor cursor) { 177f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira return new Conversation(context, cursor, false); 178f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 1796c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira 1803432b636fbcf7d6cc551b9705ed4dd88ae2b4f49Jin Cao private void buildReadContentValues() { 181f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira if (mReadContentValues == null) { 182f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mReadContentValues = new ContentValues(1); 183f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mReadContentValues.put("read", 1); 1846c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira } 1850760bfbf8259e4161ce7737fc980b6b9297885ccmindyp } 1860760bfbf8259e4161ce7737fc980b6b9297885ccmindyp 1870760bfbf8259e4161ce7737fc980b6b9297885ccmindyp /** 188f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * Marks all messages in this conversation as read and updates 189f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * relevant notifications. This method returns immediately; 1908937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira * work is dispatched to a background thread. 1918937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira */ 192c6adce3cf6887c4c6dd5005724565702751843d0mindyp public synchronized void markAsRead() { 1930fb551891757ecddccb299d9df770bb1675e3fd2Mindy Pereira // If we have no Uri to mark (as in the case of a conversation that 1948937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira // has not yet made its way to disk), there's nothing to do. 195c6adce3cf6887c4c6dd5005724565702751843d0mindyp final Uri threadUri = getUri(); 1968937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira 1978937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira new Thread(new Runnable() { 1988937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira public void run() { 199a538984fcc19e7624f2650b119ede39bf1f35846mindyp if (threadUri != null) { 2007c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein buildReadContentValues(); 2017c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein mContext.getContentResolver().update(threadUri, mReadContentValues, 2027c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein "read=0", null); 2037c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein mHasUnreadMessages = false; 2047c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein } 205d3367499e56116854ed637b1036fd71057ac0f49Mindy Pereira // Always update notifications regardless of the read state. 206866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira MessagingNotification.updateAllNotifications(mContext); 2076a3d5ce0b18f58fcfa1af0315dc8ddc7331c2c5fScott Kennedy } 2086a3d5ce0b18f58fcfa1af0315dc8ddc7331c2c5fScott Kennedy }).start(); 2096c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira } 21013e6243683cc8cb998a43d06f25d6ff1aab9d93bMindy Pereira 2110fb551891757ecddccb299d9df770bb1675e3fd2Mindy Pereira /** 21209f1ae9a9f209436c0c6b44a6e76b32b27937a01Mindy Pereira * Returns a content:// URI referring to this conversation, 21309f1ae9a9f209436c0c6b44a6e76b32b27937a01Mindy Pereira * or null if it does not exist on disk yet. 21409f1ae9a9f209436c0c6b44a6e76b32b27937a01Mindy Pereira */ 215370f868c834861e7732faaa9bdd07a0fa0105596Andy Huang public synchronized Uri getUri() { 21600ffece08e94ff5774b2a53c0adeb2f3d0815d66Mindy Pereira if (mThreadId <= 0) 2179365a826b46b0e274df88e92534f7d871eef2aa2mindyp return null; 2185cc0ab20009100e3ef259fe3d2e3ddc357f79285mindyp 2195cc0ab20009100e3ef259fe3d2e3ddc357f79285mindyp return ContentUris.withAppendedId(Threads.CONTENT_URI, mThreadId); 220042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang } 2212b55549d4fbe25e91f673a0727fc89ff755d9327Andy Huang 222042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang /** 2232b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy * Return the Uri for all messages in the given thread ID. 2242b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy * @deprecated 2252b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy */ 2262b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy public static Uri getUri(long threadId) { 2272b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy // TODO: Callers using this should really just have a Conversation 2282b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy // and call getUri() on it, but this guarantees no blocking. 2292b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy return ContentUris.withAppendedId(Threads.CONTENT_URI, threadId); 2302b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy } 2312b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy 2322b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy /** 2332b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy * Returns the thread ID of this conversation. Can be zero if 2342b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy * {@link ensureThreadId} has not been called yet. 2352b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy */ 2362b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy public synchronized long getThreadId() { 2372b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy return mThreadId; 2382b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy } 2392b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy 2402b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy /** 2412b9d80eb16156173f11a02eae4f770d8c975927cScott Kennedy * Guarantees that the conversation has been created in the database. 242512821c11d89d49908f3cfdee0b582601f500f3dJin Cao * This will make a blocking database call if it hasn't. 243512821c11d89d49908f3cfdee0b582601f500f3dJin Cao * 2446c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira * @return The thread ID of this conversation in the database 2459365a826b46b0e274df88e92534f7d871eef2aa2mindyp */ 2469365a826b46b0e274df88e92534f7d871eef2aa2mindyp public synchronized long ensureThreadId() { 2479365a826b46b0e274df88e92534f7d871eef2aa2mindyp if (DEBUG) { 2486c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira Log.v("TAG", "ensureThreadId before: " + mThreadId); 249a8ead90ce1e6c66e4ecacdf7cfa25c2cafc9bb3bMindy Pereira } 250a8ead90ce1e6c66e4ecacdf7cfa25c2cafc9bb3bMindy Pereira if (mThreadId <= 0) { 25120a97ded277fdcd3c63952a23718410c2882103fVikram Aggarwal mThreadId = getOrCreateThreadId(mContext, mRecipients); 25254f120f6ada40c7191811dfff99b151e9e192c78mindyp } 25354f120f6ada40c7191811dfff99b151e9e192c78mindyp if (DEBUG) { 25454f120f6ada40c7191811dfff99b151e9e192c78mindyp Log.v("TAG", "ensureThreadId after: " + mThreadId); 25554f120f6ada40c7191811dfff99b151e9e192c78mindyp } 25654f120f6ada40c7191811dfff99b151e9e192c78mindyp 257cff4a2b2e92258c6c85ed4af15bc13101aa71170Mindy Pereira return mThreadId; 258f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 259f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 260f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira public synchronized void clearThreadId() { 261f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira // remove ourself from the cache 262f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira Cache.remove(mThreadId); 263f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 264f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira mThreadId = 0; 2653b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp } 266e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang 2677c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein /** 268e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang * Sets the list of recipients associated with this conversation. 269e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang * If called, {@link ensureThreadId} must be called before the next 270e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang * operation that depends on this conversation existing in the 271f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * database (e.g. storing a draft message to it). 272f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira */ 273f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira public synchronized void setRecipients(ContactList list) { 2746c72a787b58a0bc3afcb71093eddf8c29d1cf5edMindy Pereira mRecipients = list; 2753b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp 2763b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp // Invalidate thread ID because the recipient set has changed. 2773b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp mThreadId = 0; 2784d4531a63cff536d2ee1a2929d0820981df8516amindyp } 2793b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp 2807c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein /** 2817c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein * Returns the recipient set of this conversation. 2827c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein */ 2837c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein public synchronized ContactList getRecipients() { 2847c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein return mRecipients; 285f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira } 286f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira 28707118a01f7183645957010779222b84930f75b4eMindy Pereira /** 28807118a01f7183645957010779222b84930f75b4eMindy Pereira * Returns true if a draft message exists in this conversation. 28907118a01f7183645957010779222b84930f75b4eMindy Pereira */ 290cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook public synchronized boolean hasDraft() { 29184f7d32bdc79263004ed5241480988e02f8e618cmindyp if (mThreadId <= 0) 29284f7d32bdc79263004ed5241480988e02f8e618cmindyp return false; 293cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook 294cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook return DraftCache.getInstance().hasDraft(mThreadId); 29584f7d32bdc79263004ed5241480988e02f8e618cmindyp } 296a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal 297a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal /** 298a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal * Sets whether or not this conversation has a draft message. 299cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook */ 300067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira public synchronized void setDraftState(boolean hasDraft) { 30184f7d32bdc79263004ed5241480988e02f8e618cmindyp if (mThreadId <= 0) 302cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook return; 303067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira 304067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira DraftCache.getInstance().setDraftState(mThreadId, hasDraft); 305cc2f9296ad63ed681c200feb118c5caace36e72emindyp } 306dbb587f15c723ae80edb33f65c29cc2b6c15eba0Scott Kennedy 307067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira /** 308067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira * Returns the time of the last update to this conversation in milliseconds, 309dbb587f15c723ae80edb33f65c29cc2b6c15eba0Scott Kennedy * on the {@link System.currentTimeMillis} timebase. 310067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira */ 311067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira public synchronized long getDate() { 312599e7f8bf95d2f21a966cbff1bf72adf77a90a33Andy Huang return mDate; 313067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira } 314067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira 315067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira /** 316067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira * Returns the number of messages in this conversation, excluding the draft 317067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira * (if it exists). 318067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira */ 319067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira public synchronized int getMessageCount() { 320067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira return mMessageCount; 321067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira } 322067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira 323067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira /** 324067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira * Returns a snippet of text from the most recent message in the conversation. 325599e7f8bf95d2f21a966cbff1bf72adf77a90a33Andy Huang */ 326067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira public synchronized String getSnippet() { 327067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira return mSnippet; 328067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira } 329067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira 330c79aec78d06928b3fa1464d6aed60019f9b4843bMindy Pereira /** 331067ef97264240a2c3f9172a9ab88ba43fef7c475Mindy Pereira * Returns true if there are any unread messages in the conversation. 33207118a01f7183645957010779222b84930f75b4eMindy Pereira */ 33307118a01f7183645957010779222b84930f75b4eMindy Pereira public synchronized boolean hasUnreadMessages() { 3340fb551891757ecddccb299d9df770bb1675e3fd2Mindy Pereira return mHasUnreadMessages; 3350fb551891757ecddccb299d9df770bb1675e3fd2Mindy Pereira } 3360fb551891757ecddccb299d9df770bb1675e3fd2Mindy Pereira 3370fb551891757ecddccb299d9df770bb1675e3fd2Mindy Pereira /** 3388937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira * Returns true if any messages in the conversation have attachments. 3398937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira */ 340f69d0394f7de5c0681b8d5d5ca0c60b55c55f815Vikram Aggarwal public synchronized boolean hasAttachment() { 341f69d0394f7de5c0681b8d5d5ca0c60b55c55f815Vikram Aggarwal return mHasAttachment; 3426126d72ae2769bd39451872f45781cadb5b90515Mark Wei } 3436126d72ae2769bd39451872f45781cadb5b90515Mark Wei 344f69d0394f7de5c0681b8d5d5ca0c60b55c55f815Vikram Aggarwal /** 3456126d72ae2769bd39451872f45781cadb5b90515Mark Wei * Returns true if any messages in the conversation are in an error state. 3461fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy */ 3476126d72ae2769bd39451872f45781cadb5b90515Mark Wei public synchronized boolean hasError() { 3481fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy return mHasError; 3496126d72ae2769bd39451872f45781cadb5b90515Mark Wei } 3506126d72ae2769bd39451872f45781cadb5b90515Mark Wei 35100ffece08e94ff5774b2a53c0adeb2f3d0815d66Mindy Pereira private static long getOrCreateThreadId(Context context, ContactList list) { 35200ffece08e94ff5774b2a53c0adeb2f3d0815d66Mindy Pereira HashSet<String> recipients = new HashSet<String>(); 353c6adce3cf6887c4c6dd5005724565702751843d0mindyp Contact cacheContact = null; 354dd9d3a16148b178cb95fd4db4168a67c64eb944fMindy Pereira for (Contact c : list) { 3558937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira cacheContact = Contact.get(c.getNumber(),true); 3565254486a8c37cdf2b579aa7a3b9c8eb7324062bcmindyp if (cacheContact != null) { 3575254486a8c37cdf2b579aa7a3b9c8eb7324062bcmindyp recipients.add(cacheContact.getNumber()); 358d064972c4a032f3c5cfcb236aa470273f7ac2ce0mindyp } else { 359d064972c4a032f3c5cfcb236aa470273f7ac2ce0mindyp recipients.add(c.getNumber()); 3605254486a8c37cdf2b579aa7a3b9c8eb7324062bcmindyp } 3619365a826b46b0e274df88e92534f7d871eef2aa2mindyp } 3629365a826b46b0e274df88e92534f7d871eef2aa2mindyp return Threads.getOrCreateThreadId(context, recipients); 3639365a826b46b0e274df88e92534f7d871eef2aa2mindyp } 3649365a826b46b0e274df88e92534f7d871eef2aa2mindyp 3659365a826b46b0e274df88e92534f7d871eef2aa2mindyp /* 3669365a826b46b0e274df88e92534f7d871eef2aa2mindyp * The primary key of a conversation is its recipient set; override 3679365a826b46b0e274df88e92534f7d871eef2aa2mindyp * equals() and hashCode() to just pass through to the internal 3689365a826b46b0e274df88e92534f7d871eef2aa2mindyp * recipient sets. 36969b440141f402669fe44dfd8924a94fd22ebccf7mindyp */ 37069b440141f402669fe44dfd8924a94fd22ebccf7mindyp @Override 37179c3e1ed9cb1e1660c5d34b7bf3f3ab5c5dd5162Andy Huang public synchronized boolean equals(Object obj) { 372479505d71969e26b0785d8e0e1b81108731cf827Mark Wei try { 37369b440141f402669fe44dfd8924a94fd22ebccf7mindyp Conversation other = (Conversation)obj; 37469b440141f402669fe44dfd8924a94fd22ebccf7mindyp return (mRecipients.equals(other.mRecipients)); 37569b440141f402669fe44dfd8924a94fd22ebccf7mindyp } catch (ClassCastException e) { 37681aea35d45b3d0191ec595562a2fcf67009845d5Mark Wei return false; 377479505d71969e26b0785d8e0e1b81108731cf827Mark Wei } 378479505d71969e26b0785d8e0e1b81108731cf827Mark Wei } 37948ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang 38048ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang @Override 38148ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang public synchronized int hashCode() { 38248ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang return mRecipients.hashCode(); 38348ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang } 38448ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang 38548ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang @Override 38648ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang public synchronized String toString() { 38748ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang return String.format("[%s] (tid %d)", mRecipients.serialize(), mThreadId); 38848ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang } 38948ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang 39048ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang /** 39148ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang * Remove any obsolete conversations sitting around on disk. 39269b440141f402669fe44dfd8924a94fd22ebccf7mindyp * @deprecated 3933b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp */ 3943b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp public static void cleanup(Context context) { 3953b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp // TODO: Get rid of this awful hack. 3963b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp context.getContentResolver().delete(Threads.OBSOLETE_THREADS_URI, null, null); 3973b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp } 3983b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp 3993b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp /** 4003b5e82b06a0af3f5ddf3667de9a2aa26eeb5f24bmindyp * Start a query for all conversations in the database on the specified 4017af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp * AsyncQueryHandler. 4027af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp * 4037af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp * @param handler An AsyncQueryHandler that will receive onQueryComplete 4047af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp * upon completion of the query 4057af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp * @param token The token that will be passed to onQueryComplete 4067af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp */ 4077af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp public static void startQueryForAll(AsyncQueryHandler handler, int token) { 4087af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp handler.cancelOperation(token); 4097af43c93d4ca90bb0a43ec790a434c95128cc99fmindyp handler.startQuery(token, null, sAllThreadsUri, 410e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER); 411e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang } 412e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang 413e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang /** 414e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang * Start a delete of the conversation with the specified thread ID. 415e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang * 416e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang * @param handler An AsyncQueryHandler that will receive onDeleteComplete 4177c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein * upon completion of the conversation being deleted 418e6f4ddc19c205796cde91d58483fa39c3cc13f1aAlice Yang * @param token The token that will be passed to onDeleteComplete 41900ffece08e94ff5774b2a53c0adeb2f3d0815d66Mindy Pereira * @param deleteAll Delete the whole thread including locked messages 420 * @param threadId Thread ID of the conversation to be deleted 421 */ 422 public static void startDelete(AsyncQueryHandler handler, int token, boolean deleteAll, 423 long threadId) { 424 Uri uri = ContentUris.withAppendedId(Threads.CONTENT_URI, threadId); 425 String selection = deleteAll ? null : "locked=0"; 426 handler.startDelete(token, null, uri, selection, null); 427 } 428 429 /** 430 * Start deleting all conversations in the database. 431 * @param handler An AsyncQueryHandler that will receive onDeleteComplete 432 * upon completion of all conversations being deleted 433 * @param token The token that will be passed to onDeleteComplete 434 * @param deleteAll Delete the whole thread including locked messages 435 */ 436 public static void startDeleteAll(AsyncQueryHandler handler, int token, boolean deleteAll) { 437 String selection = deleteAll ? null : "locked=0"; 438 handler.startDelete(token, null, Threads.CONTENT_URI, selection, null); 439 } 440 441 /** 442 * Check for locked messages in all threads or a specified thread. 443 * @param handler An AsyncQueryHandler that will receive onQueryComplete 444 * upon completion of looking for locked messages 445 * @param threadId The threadId of the thread to search. -1 means all threads 446 * @param token The token that will be passed to onQueryComplete 447 */ 448 public static void startQueryHaveLockedMessages(AsyncQueryHandler handler, long threadId, 449 int token) { 450 handler.cancelOperation(token); 451 Uri uri = MmsSms.CONTENT_LOCKED_URI; 452 if (threadId != -1) { 453 uri = ContentUris.withAppendedId(uri, threadId); 454 } 455 handler.startQuery(token, new Long(threadId), uri, 456 ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER); 457 } 458 459 /** 460 * Fill the specified conversation with the values from the specified 461 * cursor, possibly setting recipients to empty if {@value allowQuery} 462 * is false and the recipient IDs are not in cache. The cursor should 463 * be one made via {@link startQueryForAll}. 464 */ 465 private static void fillFromCursor(Context context, Conversation conv, 466 Cursor c, boolean allowQuery) { 467 synchronized (conv) { 468 conv.mThreadId = c.getLong(ID); 469 conv.mDate = c.getLong(DATE); 470 conv.mMessageCount = c.getInt(MESSAGE_COUNT); 471 472 // Replace the snippet with a default value if it's empty. 473 String snippet = MessageUtils.extractEncStrFromCursor(c, SNIPPET, SNIPPET_CS); 474 if (TextUtils.isEmpty(snippet)) { 475 snippet = context.getString(R.string.no_subject_view); 476 } 477 conv.mSnippet = snippet; 478 479 conv.mHasUnreadMessages = (c.getInt(READ) == 0); 480 conv.mHasError = (c.getInt(ERROR) != 0); 481 conv.mHasAttachment = (c.getInt(HAS_ATTACHMENT) != 0); 482 483 String recipientIds = c.getString(RECIPIENT_IDS); 484 conv.mRecipients = ContactList.getByIds(recipientIds, allowQuery); 485 } 486 } 487 488 /** 489 * Private cache for the use of the various forms of Conversation.get. 490 */ 491 private static class Cache { 492 private static Cache sInstance = new Cache(); 493 static Cache getInstance() { return sInstance; } 494 private final HashSet<Conversation> mCache; 495 private Cache() { 496 mCache = new HashSet<Conversation>(10); 497 } 498 499 /** 500 * Return the conversation with the specified thread ID, or 501 * null if it's not in cache. 502 */ 503 static Conversation get(long threadId) { 504 synchronized (sInstance) { 505 if (DEBUG) { 506 Log.v(TAG, "Conversation get with threadId: " + threadId); 507 } 508 dumpCache(); 509 for (Conversation c : sInstance.mCache) { 510 if (DEBUG) { 511 Log.v(TAG, "Conversation get() threadId: " + threadId + 512 " c.getThreadId(): " + c.getThreadId()); 513 } 514 if (c.getThreadId() == threadId) { 515 return c; 516 } 517 } 518 } 519 return null; 520 } 521 522 /** 523 * Return the conversation with the specified recipient 524 * list, or null if it's not in cache. 525 */ 526 static Conversation get(ContactList list) { 527 synchronized (sInstance) { 528 if (DEBUG) { 529 Log.v(TAG, "Conversation get with ContactList: " + list); 530 dumpCache(); 531 } 532 for (Conversation c : sInstance.mCache) { 533 if (c.getRecipients().equals(list)) { 534 return c; 535 } 536 } 537 } 538 return null; 539 } 540 541 /** 542 * Put the specified conversation in the cache. The caller 543 * should not place an already-existing conversation in the 544 * cache, but rather update it in place. 545 */ 546 static void put(Conversation c) { 547 synchronized (sInstance) { 548 // We update cache entries in place so people with long- 549 // held references get updated. 550 if (DEBUG) { 551 Log.v(TAG, "Conversation c: " + c + " put with threadid: " + c.getThreadId() + 552 " c.hash: " + c.hashCode()); 553 dumpCache(); 554 } 555 556 if (sInstance.mCache.contains(c)) { 557 throw new IllegalStateException("cache already contains " + c + 558 " threadId: " + c.mThreadId); 559 } 560 sInstance.mCache.add(c); 561 } 562 } 563 564 static void remove(long threadId) { 565 if (DEBUG) { 566 Log.v(TAG, "remove threadid: " + threadId); 567 dumpCache(); 568 } 569 for (Conversation c : sInstance.mCache) { 570 if (c.getThreadId() == threadId) { 571 sInstance.mCache.remove(c); 572 return; 573 } 574 } 575 } 576 577 static void dumpCache() { 578 if (DEBUG) { 579 synchronized (sInstance) { 580 Log.v(TAG, "Conversation dumpCache: "); 581 for (Conversation c : sInstance.mCache) { 582 Log.v(TAG, " c: " + c + " c.getThreadId(): " + c.getThreadId() + " hash: " + c.hashCode()); 583 } 584 } 585 } 586 } 587 588 /** 589 * Remove all conversations from the cache that are not in 590 * the provided set of thread IDs. 591 */ 592 static void keepOnly(Set<Long> threads) { 593 synchronized (sInstance) { 594 Iterator<Conversation> iter = sInstance.mCache.iterator(); 595 while (iter.hasNext()) { 596 Conversation c = iter.next(); 597 if (!threads.contains(c.getThreadId())) { 598 iter.remove(); 599 } 600 } 601 } 602 } 603 } 604 605 /** 606 * Set up the conversation cache. To be called once at application 607 * startup time. 608 */ 609 public static void init(final Context context) { 610 new Thread(new Runnable() { 611 public void run() { 612 cacheAllThreads(context); 613 } 614 }).start(); 615 } 616 617 private static void cacheAllThreads(Context context) { 618 synchronized (Cache.getInstance()) { 619 if (DEBUG) { 620 Log.v(TAG, "cacheAllThreads called"); 621 } 622 // Keep track of what threads are now on disk so we 623 // can discard anything removed from the cache. 624 HashSet<Long> threadsOnDisk = new HashSet<Long>(); 625 626 // Query for all conversations. 627 Cursor c = context.getContentResolver().query(sAllThreadsUri, 628 ALL_THREADS_PROJECTION, null, null, null); 629 try { 630 while (c.moveToNext()) { 631 long threadId = c.getLong(ID); 632 threadsOnDisk.add(threadId); 633 634 // Try to find this thread ID in the cache. 635 Conversation conv = Cache.get(threadId); 636 637 if (conv == null) { 638 // Make a new Conversation and put it in 639 // the cache if necessary. 640 conv = new Conversation(context, c, true); 641 try { 642 Cache.put(conv); 643 } catch (IllegalStateException e) { 644 Log.e(TAG, "Tried to add duplicate Conversation to Cache"); 645 } 646 } else { 647 // Or update in place so people with references 648 // to conversations get updated too. 649 fillFromCursor(context, conv, c, true); 650 } 651 } 652 } finally { 653 c.close(); 654 } 655 656 // Purge the cache of threads that no longer exist on disk. 657 Cache.keepOnly(threadsOnDisk); 658 } 659 } 660 661 private boolean loadFromThreadId(long threadId) { 662 Cursor c = mContext.getContentResolver().query(sAllThreadsUri, ALL_THREADS_PROJECTION, 663 "_id=" + Long.toString(threadId), null, null); 664 try { 665 if (c.moveToFirst()) { 666 fillFromCursor(mContext, this, c, true); 667 } else { 668 Log.e(TAG, "loadFromThreadId: Can't find thread ID " + threadId); 669 return false; 670 } 671 } finally { 672 c.close(); 673 } 674 return true; 675 } 676} 677