1ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickpackage com.android.mms.data;
2ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
3b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylorimport java.util.ArrayList;
4b04236e2977ac69e63cc0fa123399a584b606945Tom Taylorimport java.util.Collection;
5ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport java.util.HashSet;
6ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport java.util.Iterator;
770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrickimport java.util.Set;
8ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
9b736686638eca62aa89cb15184711ef38413cb3eTom Taylorimport android.app.Activity;
1070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrickimport android.content.AsyncQueryHandler;
11f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport android.content.ContentResolver;
12ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport android.content.ContentUris;
13ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport android.content.ContentValues;
14ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport android.content.Context;
1570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrickimport android.database.Cursor;
16ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport android.net.Uri;
176a53d30825817a8e70a3da0e591449377306959aTom Taylorimport android.os.AsyncTask;
18b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylorimport android.provider.BaseColumns;
19f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport android.provider.Telephony.Mms;
20f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport android.provider.Telephony.MmsSms;
21f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport android.provider.Telephony.Sms;
22f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport android.provider.Telephony.Sms.Conversations;
23d64419030e1fec1e751695dab3bd7236e2fb0214Roger Chenimport android.provider.Telephony.Threads;
24b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylorimport android.provider.Telephony.ThreadsColumns;
25b736686638eca62aa89cb15184711ef38413cb3eTom Taylorimport android.telephony.PhoneNumberUtils;
26ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport android.text.TextUtils;
27ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport android.util.Log;
28ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
29d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylorimport com.android.mms.LogTag;
307b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylorimport com.android.mms.MmsApp;
31f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport com.android.mms.R;
32ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport com.android.mms.transaction.MessagingNotification;
33fef537959e6beb02279e4994934ec88df080846fTom Taylorimport com.android.mms.ui.ComposeMessageActivity;
34ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport com.android.mms.ui.MessageUtils;
35ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickimport com.android.mms.util.DraftCache;
36ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
37ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick/**
38ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick * An interface for finding information about conversations and/or creating new ones.
39ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick */
40ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrickpublic class Conversation {
4113dbe96fc54f9b7190fd415d737f9a56dc409d10Wei Huang    private static final String TAG = "Mms/conv";
4207ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor    private static final boolean DEBUG = false;
43d2f67cfca13a6e415636dc253c371fb76974f5faTom Taylor    private static final boolean DELETEDEBUG = false;
44ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
45c7aa632be8e7d3ebe71f236f534ea2f0af71e04aTom Taylor    public static final Uri sAllThreadsUri =
4670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
4771bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
48c7aa632be8e7d3ebe71f236f534ea2f0af71e04aTom Taylor    public static final String[] ALL_THREADS_PROJECTION = {
4970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        Threads._ID, Threads.DATE, Threads.MESSAGE_COUNT, Threads.RECIPIENT_IDS,
5070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.ERROR,
5170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        Threads.HAS_ATTACHMENT
5270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    };
53627007213deb59ef938c80353c8f3598b01478b3Wei Huang
54c7aa632be8e7d3ebe71f236f534ea2f0af71e04aTom Taylor    public static final String[] UNREAD_PROJECTION = {
55e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang        Threads._ID,
56e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang        Threads.READ
57436042159134e8ecbc57097340a5bd81f2912574Tom Taylor    };
58627007213deb59ef938c80353c8f3598b01478b3Wei Huang
596bbfd941885c588cc5d94e345d2cfc4197134a19Tom Taylor    private static final String UNREAD_SELECTION = "(read=0 OR seen=0)";
60e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang
61627007213deb59ef938c80353c8f3598b01478b3Wei Huang    private static final String[] SEEN_PROJECTION = new String[] {
62627007213deb59ef938c80353c8f3598b01478b3Wei Huang        "seen"
63627007213deb59ef938c80353c8f3598b01478b3Wei Huang    };
64627007213deb59ef938c80353c8f3598b01478b3Wei Huang
6570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int ID             = 0;
6670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int DATE           = 1;
6770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int MESSAGE_COUNT  = 2;
6870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int RECIPIENT_IDS  = 3;
6970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int SNIPPET        = 4;
7070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int SNIPPET_CS     = 5;
7170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int READ           = 6;
7270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int ERROR          = 7;
7370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static final int HAS_ATTACHMENT = 8;
7471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
7570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
76ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    private final Context mContext;
77ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
78ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    // The thread ID of this conversation.  Can be zero in the case of a
79ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    // new conversation where the recipient set is changing as the user
80ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    // types and we have not hit the database yet to create a thread.
81ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    private long mThreadId;
8271bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
831d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick    private ContactList mRecipients;    // The current set of recipients.
8470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private long mDate;                 // The last update time.
8570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private int mMessageCount;          // Number of messages.
8670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private String mSnippet;            // Text of the most recent message.
8770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private boolean mHasUnreadMessages; // True if there are unread messages.
8870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private boolean mHasAttachment;     // True if any message has an attachment.
8970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private boolean mHasError;          // True if any message is in an error state.
90f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor    private boolean mIsChecked;         // True if user has selected the conversation for a
91f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor                                        // multi-operation such as delete.
9270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
936bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    private static ContentValues sReadContentValues;
946bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    private static boolean sLoadingThreads;
956bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    private static boolean sDeletingThreads;
966bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    private static Object sDeletingThreadsLock = new Object();
97436042159134e8ecbc57097340a5bd81f2912574Tom Taylor    private boolean mMarkAsReadBlocked;
986a53d30825817a8e70a3da0e591449377306959aTom Taylor    private boolean mMarkAsReadWaiting;
995893069282b516db7da5e16eef051cd02508eb2aTom Taylor
100ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    private Conversation(Context context) {
101ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        mContext = context;
1021d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick        mRecipients = new ContactList();
10370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        mThreadId = 0;
10470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
10571bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
106e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor    private Conversation(Context context, long threadId, boolean allowQuery) {
107c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        if (DEBUG) {
108c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            Log.v(TAG, "Conversation constructor threadId: " + threadId);
109c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        }
11070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        mContext = context;
111e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor        if (!loadFromThreadId(threadId, allowQuery)) {
1126cba8248f3e41921b03cc74a823a6347016e69baTom Taylor            mRecipients = new ContactList();
1136cba8248f3e41921b03cc74a823a6347016e69baTom Taylor            mThreadId = 0;
1146cba8248f3e41921b03cc74a823a6347016e69baTom Taylor        }
11570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
11670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
11770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private Conversation(Context context, Cursor cursor, boolean allowQuery) {
118c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        if (DEBUG) {
119c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            Log.v(TAG, "Conversation constructor cursor, allowQuery: " + allowQuery);
120c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        }
12170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        mContext = context;
12270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        fillFromCursor(context, this, cursor, allowQuery);
123ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
12471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
125ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
126627007213deb59ef938c80353c8f3598b01478b3Wei Huang     * Create a new conversation with no recipients.  {@link #setRecipients} can
127ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * be called as many times as you like; the conversation will not be
128627007213deb59ef938c80353c8f3598b01478b3Wei Huang     * created in the database until {@link #ensureThreadId} is called.
129ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
13059be3105fb43b5e9fcb363981dfa3de31c1bc2a8Wei Huang    public static Conversation createNew(Context context) {
13170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return new Conversation(context);
132ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
133ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
134ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
135ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Find the conversation matching the provided thread ID.
136ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
137e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor    public static Conversation get(Context context, long threadId, boolean allowQuery) {
138c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        if (DEBUG) {
139c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            Log.v(TAG, "Conversation get by threadId: " + threadId);
140c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        }
141e37a39111cf1e43107308e607c1c955989887c40Wei Huang        Conversation conv = Cache.get(threadId);
142e37a39111cf1e43107308e607c1c955989887c40Wei Huang        if (conv != null)
14370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            return conv;
144e37a39111cf1e43107308e607c1c955989887c40Wei Huang
145e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor        conv = new Conversation(context, threadId, allowQuery);
146e37a39111cf1e43107308e607c1c955989887c40Wei Huang        try {
147e37a39111cf1e43107308e607c1c955989887c40Wei Huang            Cache.put(conv);
148e37a39111cf1e43107308e607c1c955989887c40Wei Huang        } catch (IllegalStateException e) {
149c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            LogTag.error("Tried to add duplicate Conversation to Cache (from threadId): " + conv);
150c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            if (!Cache.replace(conv)) {
151c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                LogTag.error("get by threadId cache.replace failed on " + conv);
152c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            }
15371bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick        }
154e37a39111cf1e43107308e607c1c955989887c40Wei Huang        return conv;
155ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
15671bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
157ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
158ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Find the conversation matching the provided recipient set.
159e37a39111cf1e43107308e607c1c955989887c40Wei Huang     * When called with an empty recipient list, equivalent to {@link #createNew}.
160ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
161e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor    public static Conversation get(Context context, ContactList recipients, boolean allowQuery) {
162c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        if (DEBUG) {
163c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            Log.v(TAG, "Conversation get by recipients: " + recipients.serialize());
164c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        }
165ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        // If there are no recipients in the list, make a new conversation.
166ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        if (recipients.size() < 1) {
167ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            return createNew(context);
168ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        }
169ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
170e37a39111cf1e43107308e607c1c955989887c40Wei Huang        Conversation conv = Cache.get(recipients);
171e37a39111cf1e43107308e607c1c955989887c40Wei Huang        if (conv != null)
172e37a39111cf1e43107308e607c1c955989887c40Wei Huang            return conv;
173715e32f97bd9d8ce4b5ba650b97ba4b137150456Tom Taylor
174e37a39111cf1e43107308e607c1c955989887c40Wei Huang        long threadId = getOrCreateThreadId(context, recipients);
175e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor        conv = new Conversation(context, threadId, allowQuery);
176ef1e257cbaa7f7085eda3a9b075eca79075aab89Wink Saville        Log.d(TAG, "Conversation.get: created new conversation " + /*conv.toString()*/ "xxxxxxx");
17709a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang
17809a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang        if (!conv.getRecipients().equals(recipients)) {
179c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            LogTag.error(TAG, "Conversation.get: new conv's recipients don't match input recpients "
180ef1e257cbaa7f7085eda3a9b075eca79075aab89Wink Saville                    + /*recipients*/ "xxxxxxx");
18109a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang        }
18267ec6c54e11c19caf894b4ffce7250fb3fd96d30Wei Huang
183e37a39111cf1e43107308e607c1c955989887c40Wei Huang        try {
184e37a39111cf1e43107308e607c1c955989887c40Wei Huang            Cache.put(conv);
185e37a39111cf1e43107308e607c1c955989887c40Wei Huang        } catch (IllegalStateException e) {
186c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            LogTag.error("Tried to add duplicate Conversation to Cache (from recipients): " + conv);
187c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            if (!Cache.replace(conv)) {
188c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                LogTag.error("get by recipients cache.replace failed on " + conv);
189c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            }
19071bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick        }
191e37a39111cf1e43107308e607c1c955989887c40Wei Huang
192e37a39111cf1e43107308e607c1c955989887c40Wei Huang        return conv;
193ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
19471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
195ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
196ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Find the conversation matching in the specified Uri.  Example
197ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * forms: {@value content://mms-sms/conversations/3} or
198ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * {@value sms:+12124797990}.
199e37a39111cf1e43107308e607c1c955989887c40Wei Huang     * When called with a null Uri, equivalent to {@link #createNew}.
200ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
201e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor    public static Conversation get(Context context, Uri uri, boolean allowQuery) {
202c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        if (DEBUG) {
203c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            Log.v(TAG, "Conversation get by uri: " + uri);
204c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        }
205ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        if (uri == null) {
206ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            return createNew(context);
207ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        }
20871bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
209e37a39111cf1e43107308e607c1c955989887c40Wei Huang        if (DEBUG) Log.v(TAG, "Conversation get URI: " + uri);
210e37a39111cf1e43107308e607c1c955989887c40Wei Huang
211ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        // Handle a conversation URI
212ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        if (uri.getPathSegments().size() >= 2) {
213ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            try {
214ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick                long threadId = Long.parseLong(uri.getPathSegments().get(1));
215e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor                if (DEBUG) {
216e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor                    Log.v(TAG, "Conversation get threadId: " + threadId);
217e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor                }
218e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor                return get(context, threadId, allowQuery);
219ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            } catch (NumberFormatException exception) {
220d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor                LogTag.error("Invalid URI: " + uri);
221ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            }
222ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        }
22371bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
22449e599163acc3200e9bda4fb7825c041b67960f1jshin        String recipients = PhoneNumberUtils.replaceUnicodeDigits(getRecipients(uri))
22549e599163acc3200e9bda4fb7825c041b67960f1jshin                .replace(',', ';');
226a56495caea392de00eea50fb84ffdc06537110baTom Taylor        return get(context, ContactList.getByNumbers(recipients,
227e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor                allowQuery /* don't block */, true /* replace number */), allowQuery);
228ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
229ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
230ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
231d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor     * Returns true if the recipient in the uri matches the recipient list in this
232d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor     * conversation.
233d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor     */
234fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor    public boolean sameRecipient(Uri uri, Context context) {
235d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        int size = mRecipients.size();
236d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        if (size > 1) {
237d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor            return false;
238d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        }
239d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        if (uri == null) {
240d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor            return size == 0;
241d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        }
242fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor        ContactList incomingRecipient = null;
243d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        if (uri.getPathSegments().size() >= 2) {
244fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor            // it's a thread id for a conversation
245fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor            Conversation otherConv = get(context, uri, false);
246fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor            if (otherConv == null) {
247fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor                return false;
248fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor            }
249fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor            incomingRecipient = otherConv.mRecipients;
250fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor        } else {
251fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor            String recipient = getRecipients(uri);
252fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor            incomingRecipient = ContactList.getByNumbers(recipient,
253fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor                    false /* don't block */, false /* don't replace number */);
254d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        }
255fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor        if (DEBUG) Log.v(TAG, "sameRecipient incomingRecipient: " + incomingRecipient +
256fcca7b38fcc90a781f1507a0a135a64e3ae8f6d8Tom Taylor                " mRecipients: " + mRecipients);
257d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        return mRecipients.equals(incomingRecipient);
258d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor    }
259d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor
260d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor    /**
26170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Returns a temporary Conversation (not representing one on disk) wrapping
26270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * the contents of the provided cursor.  The cursor should be the one
263627007213deb59ef938c80353c8f3598b01478b3Wei Huang     * returned to your AsyncQueryHandler passed in to {@link #startQueryForAll}.
26470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * The recipient list of this conversation can be empty if the results
26570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * were not in cache.
26670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
26770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public static Conversation from(Context context, Cursor cursor) {
2686a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        // First look in the cache for the Conversation and return that one. That way, all the
2696a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        // people that are looking at the cached copy will get updated when fillFromCursor() is
2706a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        // called with this cursor.
2716a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        long threadId = cursor.getLong(ID);
2726a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        if (threadId > 0) {
2736a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor            Conversation conv = Cache.get(threadId);
2746a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor            if (conv != null) {
2756a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor                fillFromCursor(context, conv, cursor, false);   // update the existing conv in-place
2766a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor                return conv;
2776a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor            }
2786a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        }
2796a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        Conversation conv = new Conversation(context, cursor, false);
2806a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        try {
2816a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor            Cache.put(conv);
2826a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        } catch (IllegalStateException e) {
283c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            LogTag.error(TAG, "Tried to add duplicate Conversation to Cache (from cursor): " +
284c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                    conv);
285c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            if (!Cache.replace(conv)) {
286c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                LogTag.error("Converations.from cache.replace failed on " + conv);
287c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            }
2886a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        }
2896a78e60e584cb440c63198b87f8aba6b4c07f8caTom Taylor        return conv;
29070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
29171bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
2925893069282b516db7da5e16eef051cd02508eb2aTom Taylor    private void buildReadContentValues() {
2936bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        if (sReadContentValues == null) {
2946bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            sReadContentValues = new ContentValues(2);
2956bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            sReadContentValues.put("read", 1);
2966bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            sReadContentValues.put("seen", 1);
2975893069282b516db7da5e16eef051cd02508eb2aTom Taylor        }
2985893069282b516db7da5e16eef051cd02508eb2aTom Taylor    }
2995893069282b516db7da5e16eef051cd02508eb2aTom Taylor
30070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
301ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Marks all messages in this conversation as read and updates
302ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * relevant notifications.  This method returns immediately;
3036a53d30825817a8e70a3da0e591449377306959aTom Taylor     * work is dispatched to a background thread. This function should
3046a53d30825817a8e70a3da0e591449377306959aTom Taylor     * always be called from the UI thread.
305ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
306436042159134e8ecbc57097340a5bd81f2912574Tom Taylor    public void markAsRead() {
307d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor        if (DELETEDEBUG) {
308d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor            Contact.logWithTrace(TAG, "markAsRead mMarkAsReadWaiting: " + mMarkAsReadWaiting +
309d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                    " mMarkAsReadBlocked: " + mMarkAsReadBlocked);
310d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor        }
3116a53d30825817a8e70a3da0e591449377306959aTom Taylor        if (mMarkAsReadWaiting) {
3126a53d30825817a8e70a3da0e591449377306959aTom Taylor            // We've already been asked to mark everything as read, but we're blocked.
3136a53d30825817a8e70a3da0e591449377306959aTom Taylor            return;
3146a53d30825817a8e70a3da0e591449377306959aTom Taylor        }
3156a53d30825817a8e70a3da0e591449377306959aTom Taylor        if (mMarkAsReadBlocked) {
3166a53d30825817a8e70a3da0e591449377306959aTom Taylor            // We're blocked so record the fact that we want to mark the messages as read
3176a53d30825817a8e70a3da0e591449377306959aTom Taylor            // when we get unblocked.
3186a53d30825817a8e70a3da0e591449377306959aTom Taylor            mMarkAsReadWaiting = true;
3196a53d30825817a8e70a3da0e591449377306959aTom Taylor            return;
3206a53d30825817a8e70a3da0e591449377306959aTom Taylor        }
3216a53d30825817a8e70a3da0e591449377306959aTom Taylor        final Uri threadUri = getUri();
322fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor
323fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor        new AsyncTask<Void, Void, Void>() {
324fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor            protected Void doInBackground(Void... none) {
325d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                if (DELETEDEBUG || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
326d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                    LogTag.debug("markAsRead.doInBackground");
327fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor                }
328fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor                // If we have no Uri to mark (as in the case of a conversation that
329fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor                // has not yet made its way to disk), there's nothing to do.
330fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor                if (threadUri != null) {
3316a53d30825817a8e70a3da0e591449377306959aTom Taylor                    buildReadContentValues();
332e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang
3336a53d30825817a8e70a3da0e591449377306959aTom Taylor                    // Check the read flag first. It's much faster to do a query than
3346a53d30825817a8e70a3da0e591449377306959aTom Taylor                    // to do an update. Timing this function show it's about 10x faster to
3356a53d30825817a8e70a3da0e591449377306959aTom Taylor                    // do the query compared to the update, even when there's nothing to
3366a53d30825817a8e70a3da0e591449377306959aTom Taylor                    // update.
3376a53d30825817a8e70a3da0e591449377306959aTom Taylor                    boolean needUpdate = true;
338e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang
3396a53d30825817a8e70a3da0e591449377306959aTom Taylor                    Cursor c = mContext.getContentResolver().query(threadUri,
3406a53d30825817a8e70a3da0e591449377306959aTom Taylor                            UNREAD_PROJECTION, UNREAD_SELECTION, null, null);
3416a53d30825817a8e70a3da0e591449377306959aTom Taylor                    if (c != null) {
3426a53d30825817a8e70a3da0e591449377306959aTom Taylor                        try {
3436a53d30825817a8e70a3da0e591449377306959aTom Taylor                            needUpdate = c.getCount() > 0;
3446a53d30825817a8e70a3da0e591449377306959aTom Taylor                        } finally {
3456a53d30825817a8e70a3da0e591449377306959aTom Taylor                            c.close();
346436042159134e8ecbc57097340a5bd81f2912574Tom Taylor                        }
3476a53d30825817a8e70a3da0e591449377306959aTom Taylor                    }
348e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang
3496a53d30825817a8e70a3da0e591449377306959aTom Taylor                    if (needUpdate) {
3506a53d30825817a8e70a3da0e591449377306959aTom Taylor                        LogTag.debug("markAsRead: update read/seen for thread uri: " +
3516a53d30825817a8e70a3da0e591449377306959aTom Taylor                                threadUri);
3526bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                        mContext.getContentResolver().update(threadUri, sReadContentValues,
3536a53d30825817a8e70a3da0e591449377306959aTom Taylor                                UNREAD_SELECTION, null);
354436042159134e8ecbc57097340a5bd81f2912574Tom Taylor                    }
3556a53d30825817a8e70a3da0e591449377306959aTom Taylor                    setHasUnreadMessages(false);
3565893069282b516db7da5e16eef051cd02508eb2aTom Taylor                }
357c6be7e1a81769764ce0b51ce03406ad96944d8e5Tom Taylor                // Always update notifications regardless of the read state, which is usually
358c6be7e1a81769764ce0b51ce03406ad96944d8e5Tom Taylor                // canceling the notification of the thread that was just marked read.
359d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                MessagingNotification.blockingUpdateAllNotifications(mContext,
360d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                        MessagingNotification.THREAD_NONE);
361fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor
362fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor                return null;
363fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor            }
364fd36e337347b5f9a945806961d61f1c0b8b3514eTom Taylor        }.execute();
365ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
36671bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
3676a53d30825817a8e70a3da0e591449377306959aTom Taylor    /**
3686a53d30825817a8e70a3da0e591449377306959aTom Taylor     * Call this with false to prevent marking messages as read. The code calls this so
3696a53d30825817a8e70a3da0e591449377306959aTom Taylor     * the DB queries in markAsRead don't slow down the main query for messages. Once we've
3706a53d30825817a8e70a3da0e591449377306959aTom Taylor     * queried for all the messages (see ComposeMessageActivity.onQueryComplete), then we
3716a53d30825817a8e70a3da0e591449377306959aTom Taylor     * can mark messages as read. Only call this function on the UI thread.
3726a53d30825817a8e70a3da0e591449377306959aTom Taylor     */
373436042159134e8ecbc57097340a5bd81f2912574Tom Taylor    public void blockMarkAsRead(boolean block) {
374d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor        if (DELETEDEBUG || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
375436042159134e8ecbc57097340a5bd81f2912574Tom Taylor            LogTag.debug("blockMarkAsRead: " + block);
376436042159134e8ecbc57097340a5bd81f2912574Tom Taylor        }
377436042159134e8ecbc57097340a5bd81f2912574Tom Taylor
3786a53d30825817a8e70a3da0e591449377306959aTom Taylor        if (block != mMarkAsReadBlocked) {
3796a53d30825817a8e70a3da0e591449377306959aTom Taylor            mMarkAsReadBlocked = block;
3806a53d30825817a8e70a3da0e591449377306959aTom Taylor            if (!mMarkAsReadBlocked) {
3816a53d30825817a8e70a3da0e591449377306959aTom Taylor                if (mMarkAsReadWaiting) {
3826a53d30825817a8e70a3da0e591449377306959aTom Taylor                    mMarkAsReadWaiting = false;
3836a53d30825817a8e70a3da0e591449377306959aTom Taylor                    markAsRead();
384436042159134e8ecbc57097340a5bd81f2912574Tom Taylor                }
385436042159134e8ecbc57097340a5bd81f2912574Tom Taylor            }
386436042159134e8ecbc57097340a5bd81f2912574Tom Taylor        }
387436042159134e8ecbc57097340a5bd81f2912574Tom Taylor    }
388436042159134e8ecbc57097340a5bd81f2912574Tom Taylor
389ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
390ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Returns a content:// URI referring to this conversation,
391ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * or null if it does not exist on disk yet.
392ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
39370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized Uri getUri() {
394ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        if (mThreadId <= 0)
395ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            return null;
39671bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
397ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        return ContentUris.withAppendedId(Threads.CONTENT_URI, mThreadId);
398ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
39971bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
400ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
40170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Return the Uri for all messages in the given thread ID.
40270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * @deprecated
40370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
40470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public static Uri getUri(long threadId) {
40570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        // TODO: Callers using this should really just have a Conversation
40670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        // and call getUri() on it, but this guarantees no blocking.
40770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return ContentUris.withAppendedId(Threads.CONTENT_URI, threadId);
40870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
40971bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
41070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
411ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Returns the thread ID of this conversation.  Can be zero if
412e37a39111cf1e43107308e607c1c955989887c40Wei Huang     * {@link #ensureThreadId} has not been called yet.
413ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
41470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized long getThreadId() {
415ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        return mThreadId;
416ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
417ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
418ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
419ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Guarantees that the conversation has been created in the database.
420ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * This will make a blocking database call if it hasn't.
42171bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick     *
422ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * @return The thread ID of this conversation in the database
423ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
42470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized long ensureThreadId() {
425fef537959e6beb02279e4994934ec88df080846fTom Taylor        if (DEBUG || DELETEDEBUG) {
426d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor            LogTag.debug("ensureThreadId before: " + mThreadId);
42707ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        }
428ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        if (mThreadId <= 0) {
429ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            mThreadId = getOrCreateThreadId(mContext, mRecipients);
430ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        }
431fef537959e6beb02279e4994934ec88df080846fTom Taylor        if (DEBUG || DELETEDEBUG) {
432d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor            LogTag.debug("ensureThreadId after: " + mThreadId);
43307ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        }
43471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
435ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        return mThreadId;
436ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
437338855e07bb9c998705d4e6680f696888ca3d0e5Tom Taylor
438926da0d5c27e8d0e4246ea975c2226b83a81a5d3Tom Taylor    public synchronized void clearThreadId() {
43907ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        // remove ourself from the cache
440d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
441d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor            LogTag.debug("clearThreadId old threadId was: " + mThreadId + " now zero");
442d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        }
44307ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        Cache.remove(mThreadId);
44407ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor
445926da0d5c27e8d0e4246ea975c2226b83a81a5d3Tom Taylor        mThreadId = 0;
446338855e07bb9c998705d4e6680f696888ca3d0e5Tom Taylor    }
44771bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
448ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
449ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Sets the list of recipients associated with this conversation.
450e37a39111cf1e43107308e607c1c955989887c40Wei Huang     * If called, {@link #ensureThreadId} must be called before the next
451ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * operation that depends on this conversation existing in the
452ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * database (e.g. storing a draft message to it).
453ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
4541d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick    public synchronized void setRecipients(ContactList list) {
45510ca1d3969305df50fb07a17f5d23b0ed59f7868Tom Taylor        if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
4565a9381876ce766cc761c3e6ed2ea9a67e19bd716Tom Taylor            Log.d(TAG, "setRecipients before: " + this.toString());
45710ca1d3969305df50fb07a17f5d23b0ed59f7868Tom Taylor        }
458ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        mRecipients = list;
459ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
460ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        // Invalidate thread ID because the recipient set has changed.
461ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        mThreadId = 0;
46210ca1d3969305df50fb07a17f5d23b0ed59f7868Tom Taylor
46310ca1d3969305df50fb07a17f5d23b0ed59f7868Tom Taylor        if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
4645a9381876ce766cc761c3e6ed2ea9a67e19bd716Tom Taylor            Log.d(TAG, "setRecipients after: " + this.toString());
46510ca1d3969305df50fb07a17f5d23b0ed59f7868Tom Taylor        }
466fef537959e6beb02279e4994934ec88df080846fTom Taylor    }
46771bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
468ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
469ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Returns the recipient set of this conversation.
470ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
4711d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick    public synchronized ContactList getRecipients() {
472ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        return mRecipients;
473ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
47471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
475ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
476ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Returns true if a draft message exists in this conversation.
477ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
47870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized boolean hasDraft() {
479ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        if (mThreadId <= 0)
480ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            return false;
48171bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
482ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        return DraftCache.getInstance().hasDraft(mThreadId);
483ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
48471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
485ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    /**
486ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     * Sets whether or not this conversation has a draft message.
487ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick     */
48870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized void setDraftState(boolean hasDraft) {
489ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        if (mThreadId <= 0)
490ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick            return;
49171bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
492ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        DraftCache.getInstance().setDraftState(mThreadId, hasDraft);
493ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
49471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
49570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
49670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Returns the time of the last update to this conversation in milliseconds,
497e37a39111cf1e43107308e607c1c955989887c40Wei Huang     * on the {@link System#currentTimeMillis} timebase.
49870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
49970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized long getDate() {
50070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return mDate;
50170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
50271bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
50370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
50470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Returns the number of messages in this conversation, excluding the draft
50570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * (if it exists).
50670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
50770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized int getMessageCount() {
50870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return mMessageCount;
50970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
510c06913a292eba0dc9c17ca312f837f009b8e46f5Tom Taylor    /**
511c06913a292eba0dc9c17ca312f837f009b8e46f5Tom Taylor     * Set the number of messages in this conversation, excluding the draft
512c06913a292eba0dc9c17ca312f837f009b8e46f5Tom Taylor     * (if it exists).
513c06913a292eba0dc9c17ca312f837f009b8e46f5Tom Taylor     */
514c06913a292eba0dc9c17ca312f837f009b8e46f5Tom Taylor    public synchronized void setMessageCount(int cnt) {
515c06913a292eba0dc9c17ca312f837f009b8e46f5Tom Taylor        mMessageCount = cnt;
516c06913a292eba0dc9c17ca312f837f009b8e46f5Tom Taylor    }
51771bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
51870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
51971bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick     * Returns a snippet of text from the most recent message in the conversation.
52070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
52170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized String getSnippet() {
52270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return mSnippet;
52370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
52471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
52570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
52670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Returns true if there are any unread messages in the conversation.
52770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
528e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang    public boolean hasUnreadMessages() {
529e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang        synchronized (this) {
530e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang            return mHasUnreadMessages;
531e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang        }
532e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang    }
533e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang
534e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang    private void setHasUnreadMessages(boolean flag) {
535e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang        synchronized (this) {
536e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang            mHasUnreadMessages = flag;
537e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang        }
53870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
53970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
54070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
54170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Returns true if any messages in the conversation have attachments.
54270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
54370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized boolean hasAttachment() {
54470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return mHasAttachment;
54570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
54670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
54770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
54870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Returns true if any messages in the conversation are in an error state.
54970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
55070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized boolean hasError() {
55170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return mHasError;
55270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
55371bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
554f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor    /**
555f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor     * Returns true if this conversation is selected for a multi-operation.
556f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor     */
557f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor    public synchronized boolean isChecked() {
558f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor        return mIsChecked;
559f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor    }
560f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor
561f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor    public synchronized void setIsChecked(boolean isChecked) {
562f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor        mIsChecked = isChecked;
563f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor    }
564f9d706cfd0c46a74ba3d79e5543f13a225328d30Tom Taylor
5651d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick    private static long getOrCreateThreadId(Context context, ContactList list) {
566ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        HashSet<String> recipients = new HashSet<String>();
567baf7fec7d1a5b8d52ae7be04865f9e869742c261repo sync        Contact cacheContact = null;
5681d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick        for (Contact c : list) {
569715e32f97bd9d8ce4b5ba650b97ba4b137150456Tom Taylor            cacheContact = Contact.get(c.getNumber(), false);
570baf7fec7d1a5b8d52ae7be04865f9e869742c261repo sync            if (cacheContact != null) {
571baf7fec7d1a5b8d52ae7be04865f9e869742c261repo sync                recipients.add(cacheContact.getNumber());
572baf7fec7d1a5b8d52ae7be04865f9e869742c261repo sync            } else {
573baf7fec7d1a5b8d52ae7be04865f9e869742c261repo sync                recipients.add(c.getNumber());
574baf7fec7d1a5b8d52ae7be04865f9e869742c261repo sync            }
575ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        }
5766bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        synchronized(sDeletingThreadsLock) {
577fef537959e6beb02279e4994934ec88df080846fTom Taylor            if (DELETEDEBUG) {
578fef537959e6beb02279e4994934ec88df080846fTom Taylor                ComposeMessageActivity.log("Conversation getOrCreateThreadId for: " +
579fef537959e6beb02279e4994934ec88df080846fTom Taylor                        list.formatNamesAndNumbers(",") + " sDeletingThreads: " + sDeletingThreads);
580fef537959e6beb02279e4994934ec88df080846fTom Taylor            }
5816bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            long now = System.currentTimeMillis();
5826bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            while (sDeletingThreads) {
5836bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                try {
5846bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    sDeletingThreadsLock.wait(30000);
5856bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                } catch (InterruptedException e) {
5866bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                }
5876bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                if (System.currentTimeMillis() - now > 29000) {
5886bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    // The deleting thread task is stuck or onDeleteComplete wasn't called.
5896bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    // Unjam ourselves.
5906bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    Log.e(TAG, "getOrCreateThreadId timed out waiting for delete to complete",
5916bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                            new Exception());
5926bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    sDeletingThreads = false;
5936bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    break;
5946bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                }
5956bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            }
5966bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            long retVal = Threads.getOrCreateThreadId(context, recipients);
597fef537959e6beb02279e4994934ec88df080846fTom Taylor            if (DELETEDEBUG || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
5986bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                LogTag.debug("[Conversation] getOrCreateThreadId for (%s) returned %d",
5996bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                        recipients, retVal);
6006bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            }
6016bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            return retVal;
602d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor        }
6036bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    }
60409a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang
6056bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    public static long getOrCreateThreadId(Context context, String address) {
6066bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        synchronized(sDeletingThreadsLock) {
607fef537959e6beb02279e4994934ec88df080846fTom Taylor            if (DELETEDEBUG) {
608fef537959e6beb02279e4994934ec88df080846fTom Taylor                ComposeMessageActivity.log("Conversation getOrCreateThreadId for: " +
609fef537959e6beb02279e4994934ec88df080846fTom Taylor                        address + " sDeletingThreads: " + sDeletingThreads);
610fef537959e6beb02279e4994934ec88df080846fTom Taylor            }
6116bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            long now = System.currentTimeMillis();
6126bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            while (sDeletingThreads) {
6136bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                try {
6146bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    sDeletingThreadsLock.wait(30000);
6156bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                } catch (InterruptedException e) {
6166bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                }
6176bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                if (System.currentTimeMillis() - now > 29000) {
6186bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    // The deleting thread task is stuck or onDeleteComplete wasn't called.
6196bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    // Unjam ourselves.
6206bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    Log.e(TAG, "getOrCreateThreadId timed out waiting for delete to complete",
6216bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                            new Exception());
6226bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    sDeletingThreads = false;
6236bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    break;
6246bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                }
6256bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            }
6266bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            long retVal = Threads.getOrCreateThreadId(context, address);
627fef537959e6beb02279e4994934ec88df080846fTom Taylor            if (DELETEDEBUG || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
6286bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                LogTag.debug("[Conversation] getOrCreateThreadId for (%s) returned %d",
6296bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                        address, retVal);
6306bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            }
6316bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            return retVal;
6326bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        }
633ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
634ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick
63570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /*
63670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * The primary key of a conversation is its recipient set; override
63770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * equals() and hashCode() to just pass through to the internal
63870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * recipient sets.
63970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
64070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    @Override
64170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized boolean equals(Object obj) {
64270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        try {
64370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            Conversation other = (Conversation)obj;
64470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            return (mRecipients.equals(other.mRecipients));
64570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        } catch (ClassCastException e) {
64670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            return false;
64770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
64870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
64971bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
650ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    @Override
65170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized int hashCode() {
65270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        return mRecipients.hashCode();
65370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
65471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
65570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    @Override
65670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public synchronized String toString() {
657ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick        return String.format("[%s] (tid %d)", mRecipients.serialize(), mThreadId);
658ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick    }
65971bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
66070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
66163e395258ae33a101b9634a68b55ec89fac8b580Tom Taylor     * Remove any obsolete conversations sitting around on disk. Obsolete threads are threads
66263e395258ae33a101b9634a68b55ec89fac8b580Tom Taylor     * that aren't referenced by any message in the pdu or sms tables.
66370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
66463e395258ae33a101b9634a68b55ec89fac8b580Tom Taylor    public static void asyncDeleteObsoleteThreads(AsyncQueryHandler handler, int token) {
66563e395258ae33a101b9634a68b55ec89fac8b580Tom Taylor        handler.startDelete(token, null, Threads.OBSOLETE_THREADS_URI, null, null);
66670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
66771bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
66870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
66970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Start a query for all conversations in the database on the specified
67070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * AsyncQueryHandler.
67171bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick     *
67270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * @param handler An AsyncQueryHandler that will receive onQueryComplete
67370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     *                upon completion of the query
67470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * @param token   The token that will be passed to onQueryComplete
67570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
67670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public static void startQueryForAll(AsyncQueryHandler handler, int token) {
67770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        handler.cancelOperation(token);
6783b21f6ab04db5936d73e9f53032f1587389380ffTom Taylor
6793b21f6ab04db5936d73e9f53032f1587389380ffTom Taylor        // This query looks like this in the log:
6803b21f6ab04db5936d73e9f53032f1587389380ffTom Taylor        // I/Database(  147): elapsedTime4Sql|/data/data/com.android.providers.telephony/databases/
6813b21f6ab04db5936d73e9f53032f1587389380ffTom Taylor        // mmssms.db|2.253 ms|SELECT _id, date, message_count, recipient_ids, snippet, snippet_cs,
6823b21f6ab04db5936d73e9f53032f1587389380ffTom Taylor        // read, error, has_attachment FROM threads ORDER BY  date DESC
6833b21f6ab04db5936d73e9f53032f1587389380ffTom Taylor
6842426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor        startQuery(handler, token, null);
6852426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor    }
6862426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor
6872426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor    /**
6882426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     * Start a query for in the database on the specified AsyncQueryHandler with the specified
6892426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     * "where" clause.
6902426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     *
6912426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     * @param handler An AsyncQueryHandler that will receive onQueryComplete
6922426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     *                upon completion of the query
6932426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     * @param token   The token that will be passed to onQueryComplete
6942426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     * @param selection   A where clause (can be null) to select particular conv items.
6952426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor     */
6962426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor    public static void startQuery(AsyncQueryHandler handler, int token, String selection) {
6972426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor        handler.cancelOperation(token);
6982426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor
6992426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor        // This query looks like this in the log:
7002426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor        // I/Database(  147): elapsedTime4Sql|/data/data/com.android.providers.telephony/databases/
7012426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor        // mmssms.db|2.253 ms|SELECT _id, date, message_count, recipient_ids, snippet, snippet_cs,
7022426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor        // read, error, has_attachment FROM threads ORDER BY  date DESC
7032426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor
70470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        handler.startQuery(token, null, sAllThreadsUri,
7052426db8887f7f1c0c93dbab5a10663cb22575ccdTom Taylor                ALL_THREADS_PROJECTION, selection, null, Conversations.DEFAULT_SORT_ORDER);
70670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
70771bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
70870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
70970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Start a delete of the conversation with the specified thread ID.
71070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     *
71170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * @param handler An AsyncQueryHandler that will receive onDeleteComplete
71270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     *                upon completion of the conversation being deleted
71370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * @param token   The token that will be passed to onDeleteComplete
714475d780550e0034379ed821a90fdbc96d810b72dTom Taylor     * @param deleteAll Delete the whole thread including locked messages
7158cc338d324dc4c92b686029064882147e9054f17Tom Taylor     * @param threadIds Collection of thread IDs of the conversations to be deleted
71670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
7176bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    public static void startDelete(ConversationQueryHandler handler, int token, boolean deleteAll,
7188cc338d324dc4c92b686029064882147e9054f17Tom Taylor            Collection<Long> threadIds) {
7196bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        synchronized(sDeletingThreadsLock) {
720fef537959e6beb02279e4994934ec88df080846fTom Taylor            if (DELETEDEBUG) {
721fef537959e6beb02279e4994934ec88df080846fTom Taylor                Log.v(TAG, "Conversation startDelete sDeletingThreads: " +
7228cc338d324dc4c92b686029064882147e9054f17Tom Taylor                        sDeletingThreads);
723fef537959e6beb02279e4994934ec88df080846fTom Taylor            }
7246bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            if (sDeletingThreads) {
7256bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                Log.e(TAG, "startDeleteAll already in the middle of a delete", new Exception());
7266bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            }
7278cc338d324dc4c92b686029064882147e9054f17Tom Taylor            MmsApp.getApplication().getPduLoaderManager().clear();
7286bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            sDeletingThreads = true;
7297b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor
7308cc338d324dc4c92b686029064882147e9054f17Tom Taylor            for (long threadId : threadIds) {
7318cc338d324dc4c92b686029064882147e9054f17Tom Taylor                Uri uri = ContentUris.withAppendedId(Threads.CONTENT_URI, threadId);
7328cc338d324dc4c92b686029064882147e9054f17Tom Taylor                String selection = deleteAll ? null : "locked=0";
7337b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor
7348cc338d324dc4c92b686029064882147e9054f17Tom Taylor                handler.setDeleteToken(token);
7358cc338d324dc4c92b686029064882147e9054f17Tom Taylor                handler.startDelete(token, new Long(threadId), uri, selection, null);
7368cc338d324dc4c92b686029064882147e9054f17Tom Taylor
7378cc338d324dc4c92b686029064882147e9054f17Tom Taylor                DraftCache.getInstance().setDraftState(threadId, false);
7388cc338d324dc4c92b686029064882147e9054f17Tom Taylor            }
7396bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        }
74070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
74170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
74270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
74370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Start deleting all conversations in the database.
74470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * @param handler An AsyncQueryHandler that will receive onDeleteComplete
74570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     *                upon completion of all conversations being deleted
74670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * @param token   The token that will be passed to onDeleteComplete
747475d780550e0034379ed821a90fdbc96d810b72dTom Taylor     * @param deleteAll Delete the whole thread including locked messages
74870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
7496bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    public static void startDeleteAll(ConversationQueryHandler handler, int token,
7506bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            boolean deleteAll) {
7516bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        synchronized(sDeletingThreadsLock) {
752fef537959e6beb02279e4994934ec88df080846fTom Taylor            if (DELETEDEBUG) {
753fef537959e6beb02279e4994934ec88df080846fTom Taylor                Log.v(TAG, "Conversation startDeleteAll sDeletingThreads: " +
754fef537959e6beb02279e4994934ec88df080846fTom Taylor                                sDeletingThreads);
755fef537959e6beb02279e4994934ec88df080846fTom Taylor            }
7566bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            if (sDeletingThreads) {
7576bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                Log.e(TAG, "startDeleteAll already in the middle of a delete", new Exception());
7586bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            }
7596bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            sDeletingThreads = true;
7606bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            String selection = deleteAll ? null : "locked=0";
7617b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor
762c6be7e1a81769764ce0b51ce03406ad96944d8e5Tom Taylor            MmsApp app = MmsApp.getApplication();
763c6be7e1a81769764ce0b51ce03406ad96944d8e5Tom Taylor            app.getPduLoaderManager().clear();
764c6be7e1a81769764ce0b51ce03406ad96944d8e5Tom Taylor            app.getThumbnailManager().clear();
7657b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor
7666bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            handler.setDeleteToken(token);
7676bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            handler.startDelete(token, new Long(-1), Threads.CONTENT_URI, selection, null);
7686bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        }
7696bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    }
7706bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor
7716bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor    public static class ConversationQueryHandler extends AsyncQueryHandler {
7726bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        private int mDeleteToken;
7736bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor
7746bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        public ConversationQueryHandler(ContentResolver cr) {
7756bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            super(cr);
7766bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        }
7776bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor
7786bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        public void setDeleteToken(int token) {
7796bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            mDeleteToken = token;
7806bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        }
7816bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor
7826bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        /**
7836bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor         * Always call this super method from your overridden onDeleteComplete function.
7846bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor         */
7856bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        @Override
7866bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        protected void onDeleteComplete(int token, Object cookie, int result) {
7876bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            if (token == mDeleteToken) {
7886bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                // Test code
7896bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor//                try {
7906bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor//                    Thread.sleep(10000);
7916bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor//                } catch (InterruptedException e) {
7926bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor//                }
7936bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor
7946bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                // release lock
7956bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                synchronized(sDeletingThreadsLock) {
7966bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    sDeletingThreads = false;
797fef537959e6beb02279e4994934ec88df080846fTom Taylor                    if (DELETEDEBUG) {
798fef537959e6beb02279e4994934ec88df080846fTom Taylor                        Log.v(TAG, "Conversation onDeleteComplete sDeletingThreads: " +
799fef537959e6beb02279e4994934ec88df080846fTom Taylor                                        sDeletingThreads);
800fef537959e6beb02279e4994934ec88df080846fTom Taylor                    }
8016bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                    sDeletingThreadsLock.notifyAll();
8026bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                }
8036bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            }
8046bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor        }
80570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
80671bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
80770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
80885fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor     * Check for locked messages in all threads or a specified thread.
80985fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor     * @param handler An AsyncQueryHandler that will receive onQueryComplete
81085fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor     *                upon completion of looking for locked messages
811b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor     * @param threadIds   A list of threads to search. null means all threads
81285fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor     * @param token   The token that will be passed to onQueryComplete
81385fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor     */
814b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor    public static void startQueryHaveLockedMessages(AsyncQueryHandler handler,
815b04236e2977ac69e63cc0fa123399a584b606945Tom Taylor            Collection<Long> threadIds,
81685fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor            int token) {
81785fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor        handler.cancelOperation(token);
81885fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor        Uri uri = MmsSms.CONTENT_LOCKED_URI;
819b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor
820b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor        String selection = null;
821b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor        if (threadIds != null) {
822b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            StringBuilder buf = new StringBuilder();
823b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            int i = 0;
824b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor
825b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            for (long threadId : threadIds) {
826b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor                if (i++ > 0) {
827b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor                    buf.append(" OR ");
828b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor                }
829b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor                // We have to build the selection arg into the selection because deep down in
830b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor                // provider, the function buildUnionSubQuery takes selectionArgs, but ignores it.
831b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor                buf.append(Mms.THREAD_ID).append("=").append(Long.toString(threadId));
832b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            }
833b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            selection = buf.toString();
834b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor        }
835b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor        handler.startQuery(token, threadIds, uri,
836b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor                ALL_THREADS_PROJECTION, selection, null, Conversations.DEFAULT_SORT_ORDER);
837b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor    }
838b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor
839b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor    /**
840b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor     * Check for locked messages in all threads or a specified thread.
841b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor     * @param handler An AsyncQueryHandler that will receive onQueryComplete
842b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor     *                upon completion of looking for locked messages
843b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor     * @param threadId   The threadId of the thread to search. -1 means all threads
844b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor     * @param token   The token that will be passed to onQueryComplete
845b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor     */
846b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor    public static void startQueryHaveLockedMessages(AsyncQueryHandler handler,
847b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            long threadId,
848b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            int token) {
849b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor        ArrayList<Long> threadIds = null;
85085fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor        if (threadId != -1) {
851b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            threadIds = new ArrayList<Long>();
852b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor            threadIds.add(threadId);
85385fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor        }
854b51ea8318ca2e4019b666d938e3e7efdf6e643b3Tom Taylor        startQueryHaveLockedMessages(handler, threadIds, token);
85585fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor    }
85685fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor
85785fbc94e34688455c3f2f36271ba65b90ceb2542Tom Taylor    /**
85870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Fill the specified conversation with the values from the specified
85970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * cursor, possibly setting recipients to empty if {@value allowQuery}
86070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * is false and the recipient IDs are not in cache.  The cursor should
861e37a39111cf1e43107308e607c1c955989887c40Wei Huang     * be one made via {@link #startQueryForAll}.
86270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
86370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static void fillFromCursor(Context context, Conversation conv,
86470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                                       Cursor c, boolean allowQuery) {
86570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        synchronized (conv) {
866338855e07bb9c998705d4e6680f696888ca3d0e5Tom Taylor            conv.mThreadId = c.getLong(ID);
86770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            conv.mDate = c.getLong(DATE);
86870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            conv.mMessageCount = c.getInt(MESSAGE_COUNT);
86970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
87070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            // Replace the snippet with a default value if it's empty.
871ff3e6009a8180701d8f344f9c128c79610e8bcfbTom Taylor            String snippet = MessageUtils.cleanseMmsSubject(context,
872ff3e6009a8180701d8f344f9c128c79610e8bcfbTom Taylor                    MessageUtils.extractEncStrFromCursor(c, SNIPPET, SNIPPET_CS));
87370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            if (TextUtils.isEmpty(snippet)) {
87470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                snippet = context.getString(R.string.no_subject_view);
87570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            }
87670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            conv.mSnippet = snippet;
87770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
878e14b79584cca1bf7ba60c53bd7e3d6386adbfc59Wei Huang            conv.setHasUnreadMessages(c.getInt(READ) == 0);
87970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            conv.mHasError = (c.getInt(ERROR) != 0);
88070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            conv.mHasAttachment = (c.getInt(HAS_ATTACHMENT) != 0);
8815544f47dbe1ffd4af73b9b9dde0e35cd257cd795Tom Taylor        }
8825544f47dbe1ffd4af73b9b9dde0e35cd257cd795Tom Taylor        // Fill in as much of the conversation as we can before doing the slow stuff of looking
8835544f47dbe1ffd4af73b9b9dde0e35cd257cd795Tom Taylor        // up the contacts associated with this conversation.
8846bbfd941885c588cc5d94e345d2cfc4197134a19Tom Taylor        String recipientIds = c.getString(RECIPIENT_IDS);
885e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor        ContactList recipients = ContactList.getByIds(recipientIds, allowQuery);
8865544f47dbe1ffd4af73b9b9dde0e35cd257cd795Tom Taylor        synchronized (conv) {
8875544f47dbe1ffd4af73b9b9dde0e35cd257cd795Tom Taylor            conv.mRecipients = recipients;
88870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
88909a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang
89009a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang        if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
8915a9381876ce766cc761c3e6ed2ea9a67e19bd716Tom Taylor            Log.d(TAG, "fillFromCursor: conv=" + conv + ", recipientIds=" + recipientIds);
89209a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang        }
89370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
89470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick
89570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
89670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Private cache for the use of the various forms of Conversation.get.
89770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
89870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    private static class Cache {
89970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        private static Cache sInstance = new Cache();
90070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        static Cache getInstance() { return sInstance; }
90170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        private final HashSet<Conversation> mCache;
90270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        private Cache() {
90370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            mCache = new HashSet<Conversation>(10);
90470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
90571bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
90670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        /**
90770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * Return the conversation with the specified thread ID, or
90870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * null if it's not in cache.
90970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         */
91070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        static Conversation get(long threadId) {
91170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            synchronized (sInstance) {
91209a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
913d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor                    LogTag.debug("Conversation get with threadId: " + threadId);
91407ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                }
91570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                for (Conversation c : sInstance.mCache) {
91607ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                    if (DEBUG) {
917d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor                        LogTag.debug("Conversation get() threadId: " + threadId +
91807ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                                " c.getThreadId(): " + c.getThreadId());
91907ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                    }
92070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                    if (c.getThreadId() == threadId) {
92170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                        return c;
92270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                    }
92370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                }
92470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            }
92570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            return null;
92670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
92771bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
92870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        /**
92970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * Return the conversation with the specified recipient
93070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * list, or null if it's not in cache.
93170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         */
9321d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick        static Conversation get(ContactList list) {
93370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            synchronized (sInstance) {
93409a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
935d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor                    LogTag.debug("Conversation get with ContactList: " + list);
93607ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                }
93770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                for (Conversation c : sInstance.mCache) {
9381d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick                    if (c.getRecipients().equals(list)) {
93970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                        return c;
94070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                    }
94170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                }
94270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            }
94370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            return null;
94470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
94571bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
94670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        /**
94770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * Put the specified conversation in the cache.  The caller
94870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * should not place an already-existing conversation in the
94970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * cache, but rather update it in place.
95070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         */
95170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        static void put(Conversation c) {
95270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            synchronized (sInstance) {
95370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                // We update cache entries in place so people with long-
95470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                // held references get updated.
95509a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
9565a9381876ce766cc761c3e6ed2ea9a67e19bd716Tom Taylor                    Log.d(TAG, "Conversation.Cache.put: conv= " + c + ", hash: " + c.hashCode());
95707ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                }
95807ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor
95970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                if (sInstance.mCache.contains(c)) {
960c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                    if (DEBUG) {
961c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                        dumpCache();
962c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                    }
963338855e07bb9c998705d4e6680f696888ca3d0e5Tom Taylor                    throw new IllegalStateException("cache already contains " + c +
964338855e07bb9c998705d4e6680f696888ca3d0e5Tom Taylor                            " threadId: " + c.mThreadId);
96570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                }
96670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                sInstance.mCache.add(c);
96770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            }
96870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
96971bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
970c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        /**
971c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         * Replace the specified conversation in the cache. This is used in cases where we
972c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         * lookup a conversation in the cache by threadId, but don't find it. The caller
973c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         * then builds a new conversation (from the cursor) and tries to add it, but gets
974c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         * an exception that the conversation is already in the cache, because the hash
975c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         * is based on the recipients and it's there under a stale threadId. In this function
976c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         * we remove the stale entry and add the new one. Returns true if the operation is
977c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         * successful
978c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor         */
979c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        static boolean replace(Conversation c) {
980c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            synchronized (sInstance) {
981c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
982c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                    LogTag.debug("Conversation.Cache.put: conv= " + c + ", hash: " + c.hashCode());
983c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                }
984c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor
985c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                if (!sInstance.mCache.contains(c)) {
986c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                    if (DEBUG) {
987c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                        dumpCache();
988c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                    }
989c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                    return false;
990c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                }
991c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                // Here it looks like we're simply removing and then re-adding the same object
992c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                // to the hashset. Because the hashkey is the conversation's recipients, and not
993c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                // the thread id, we'll actually remove the object with the stale threadId and
994c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                // then add the the conversation with updated threadId, both having the same
995c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                // recipients.
996c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                sInstance.mCache.remove(c);
997c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                sInstance.mCache.add(c);
998c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                return true;
999c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor            }
1000c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor        }
1001c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor
100207ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        static void remove(long threadId) {
1003e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor            synchronized (sInstance) {
1004e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                if (DEBUG) {
1005e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                    LogTag.debug("remove threadid: " + threadId);
1006e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                    dumpCache();
1007e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                }
1008e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                for (Conversation c : sInstance.mCache) {
1009e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                    if (c.getThreadId() == threadId) {
1010e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                        sInstance.mCache.remove(c);
1011e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                        return;
1012e2986af492c30ad92c96bcb7adf8092d4d948565Tom Taylor                    }
101307ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                }
101407ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor            }
101507ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        }
101607ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor
101707ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        static void dumpCache() {
101809a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang            synchronized (sInstance) {
101909a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                LogTag.debug("Conversation dumpCache: ");
102009a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                for (Conversation c : sInstance.mCache) {
102109a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                    LogTag.debug("   conv: " + c.toString() + " hash: " + c.hashCode());
102207ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor                }
102307ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor            }
102407ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor        }
102507ce1878a36d2df1707dd4bbd9cd7235679bdc94Tom Taylor
102670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        /**
102770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * Remove all conversations from the cache that are not in
102870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         * the provided set of thread IDs.
102970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick         */
103070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        static void keepOnly(Set<Long> threads) {
103170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            synchronized (sInstance) {
103270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                Iterator<Conversation> iter = sInstance.mCache.iterator();
103370c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                while (iter.hasNext()) {
103470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                    Conversation c = iter.next();
103570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                    if (!threads.contains(c.getThreadId())) {
103670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                        iter.remove();
103770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                    }
103870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                }
103970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            }
10400f1fb760aa6a1ae040703b8ce405c96923e40603Tom Taylor            if (DEBUG) {
10410f1fb760aa6a1ae040703b8ce405c96923e40603Tom Taylor                LogTag.debug("after keepOnly");
10420f1fb760aa6a1ae040703b8ce405c96923e40603Tom Taylor                dumpCache();
10430f1fb760aa6a1ae040703b8ce405c96923e40603Tom Taylor            }
104470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
104570c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
104671bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
104770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    /**
104870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * Set up the conversation cache.  To be called once at application
104970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     * startup time.
105070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick     */
105170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    public static void init(final Context context) {
1052ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev        Thread thread = new Thread(new Runnable() {
1053ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev                @Override
1054ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev                public void run() {
1055ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev                    cacheAllThreads(context);
1056ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev                }
1057ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev            }, "Conversation.init");
1058ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev        thread.setPriority(Thread.MIN_PRIORITY);
1059ff013e77fd2e30fe98f8935ae04802a2b7fbf714Todor Kalaydjiev        thread.start();
106070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
10611d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick
1062627007213deb59ef938c80353c8f3598b01478b3Wei Huang    public static void markAllConversationsAsSeen(final Context context) {
1063d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor        if (DELETEDEBUG || DEBUG) {
1064d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor            Contact.logWithTrace(TAG, "Conversation.markAllConversationsAsSeen");
1065627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1066627007213deb59ef938c80353c8f3598b01478b3Wei Huang
10678112b6847de98297068915e7d95a87a8dc842c64Todor Kalaydjiev        Thread thread = new Thread(new Runnable() {
1068ddd31c4011b4191035bdfbba05a8edb1785f71afTodor Kalaydjiev            @Override
1069627007213deb59ef938c80353c8f3598b01478b3Wei Huang            public void run() {
1070d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                if (DELETEDEBUG) {
1071d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                    Log.d(TAG, "Conversation.markAllConversationsAsSeen.run");
1072d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor                }
1073627007213deb59ef938c80353c8f3598b01478b3Wei Huang                blockingMarkAllSmsMessagesAsSeen(context);
1074627007213deb59ef938c80353c8f3598b01478b3Wei Huang                blockingMarkAllMmsMessagesAsSeen(context);
1075627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1076627007213deb59ef938c80353c8f3598b01478b3Wei Huang                // Always update notifications regardless of the read state.
1077bc7f97ae7d3661a44211d404fe4e187c35afadceTom Taylor                MessagingNotification.blockingUpdateAllNotifications(context,
1078bc7f97ae7d3661a44211d404fe4e187c35afadceTom Taylor                        MessagingNotification.THREAD_NONE);
1079627007213deb59ef938c80353c8f3598b01478b3Wei Huang            }
10808112b6847de98297068915e7d95a87a8dc842c64Todor Kalaydjiev        }, "Conversation.markAllConversationsAsSeen");
10818112b6847de98297068915e7d95a87a8dc842c64Todor Kalaydjiev        thread.setPriority(Thread.MIN_PRIORITY);
10828112b6847de98297068915e7d95a87a8dc842c64Todor Kalaydjiev        thread.start();
1083627007213deb59ef938c80353c8f3598b01478b3Wei Huang    }
1084627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1085627007213deb59ef938c80353c8f3598b01478b3Wei Huang    private static void blockingMarkAllSmsMessagesAsSeen(final Context context) {
1086627007213deb59ef938c80353c8f3598b01478b3Wei Huang        ContentResolver resolver = context.getContentResolver();
1087f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylor        Cursor cursor = resolver.query(Sms.Inbox.CONTENT_URI,
1088627007213deb59ef938c80353c8f3598b01478b3Wei Huang                SEEN_PROJECTION,
1089627007213deb59ef938c80353c8f3598b01478b3Wei Huang                "seen=0",
1090627007213deb59ef938c80353c8f3598b01478b3Wei Huang                null,
1091627007213deb59ef938c80353c8f3598b01478b3Wei Huang                null);
1092627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1093627007213deb59ef938c80353c8f3598b01478b3Wei Huang        int count = 0;
1094627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1095627007213deb59ef938c80353c8f3598b01478b3Wei Huang        if (cursor != null) {
1096627007213deb59ef938c80353c8f3598b01478b3Wei Huang            try {
1097627007213deb59ef938c80353c8f3598b01478b3Wei Huang                count = cursor.getCount();
1098627007213deb59ef938c80353c8f3598b01478b3Wei Huang            } finally {
1099627007213deb59ef938c80353c8f3598b01478b3Wei Huang                cursor.close();
1100627007213deb59ef938c80353c8f3598b01478b3Wei Huang            }
1101627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1102627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1103627007213deb59ef938c80353c8f3598b01478b3Wei Huang        if (count == 0) {
1104627007213deb59ef938c80353c8f3598b01478b3Wei Huang            return;
1105627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1106627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1107d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor        if (DELETEDEBUG || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
1108627007213deb59ef938c80353c8f3598b01478b3Wei Huang            Log.d(TAG, "mark " + count + " SMS msgs as seen");
1109627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1110627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1111627007213deb59ef938c80353c8f3598b01478b3Wei Huang        ContentValues values = new ContentValues(1);
1112627007213deb59ef938c80353c8f3598b01478b3Wei Huang        values.put("seen", 1);
1113627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1114f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylor        resolver.update(Sms.Inbox.CONTENT_URI,
1115627007213deb59ef938c80353c8f3598b01478b3Wei Huang                values,
1116627007213deb59ef938c80353c8f3598b01478b3Wei Huang                "seen=0",
1117627007213deb59ef938c80353c8f3598b01478b3Wei Huang                null);
1118627007213deb59ef938c80353c8f3598b01478b3Wei Huang    }
1119627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1120627007213deb59ef938c80353c8f3598b01478b3Wei Huang    private static void blockingMarkAllMmsMessagesAsSeen(final Context context) {
1121627007213deb59ef938c80353c8f3598b01478b3Wei Huang        ContentResolver resolver = context.getContentResolver();
1122f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylor        Cursor cursor = resolver.query(Mms.Inbox.CONTENT_URI,
1123627007213deb59ef938c80353c8f3598b01478b3Wei Huang                SEEN_PROJECTION,
1124627007213deb59ef938c80353c8f3598b01478b3Wei Huang                "seen=0",
1125627007213deb59ef938c80353c8f3598b01478b3Wei Huang                null,
1126627007213deb59ef938c80353c8f3598b01478b3Wei Huang                null);
1127627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1128627007213deb59ef938c80353c8f3598b01478b3Wei Huang        int count = 0;
1129627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1130627007213deb59ef938c80353c8f3598b01478b3Wei Huang        if (cursor != null) {
1131627007213deb59ef938c80353c8f3598b01478b3Wei Huang            try {
1132627007213deb59ef938c80353c8f3598b01478b3Wei Huang                count = cursor.getCount();
1133627007213deb59ef938c80353c8f3598b01478b3Wei Huang            } finally {
1134627007213deb59ef938c80353c8f3598b01478b3Wei Huang                cursor.close();
1135627007213deb59ef938c80353c8f3598b01478b3Wei Huang            }
1136627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1137627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1138627007213deb59ef938c80353c8f3598b01478b3Wei Huang        if (count == 0) {
1139627007213deb59ef938c80353c8f3598b01478b3Wei Huang            return;
1140627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1141627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1142d645c8b53ae904bc059ee1ca7232916637c223e5Tom Taylor        if (DELETEDEBUG || Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
1143627007213deb59ef938c80353c8f3598b01478b3Wei Huang            Log.d(TAG, "mark " + count + " MMS msgs as seen");
1144627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1145627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1146627007213deb59ef938c80353c8f3598b01478b3Wei Huang        ContentValues values = new ContentValues(1);
1147627007213deb59ef938c80353c8f3598b01478b3Wei Huang        values.put("seen", 1);
1148627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1149f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylor        resolver.update(Mms.Inbox.CONTENT_URI,
1150627007213deb59ef938c80353c8f3598b01478b3Wei Huang                values,
1151627007213deb59ef938c80353c8f3598b01478b3Wei Huang                "seen=0",
1152627007213deb59ef938c80353c8f3598b01478b3Wei Huang                null);
1153627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1154627007213deb59ef938c80353c8f3598b01478b3Wei Huang    }
1155627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1156445fb5abf8804279e591d1c35657c9162625135eTom Taylor    /**
1157445fb5abf8804279e591d1c35657c9162625135eTom Taylor     * Are we in the process of loading and caching all the threads?.
1158445fb5abf8804279e591d1c35657c9162625135eTom Taylor     */
1159627007213deb59ef938c80353c8f3598b01478b3Wei Huang    public static boolean loadingThreads() {
1160627007213deb59ef938c80353c8f3598b01478b3Wei Huang        synchronized (Cache.getInstance()) {
11616bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            return sLoadingThreads;
1162627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1163627007213deb59ef938c80353c8f3598b01478b3Wei Huang    }
1164627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1165627007213deb59ef938c80353c8f3598b01478b3Wei Huang    private static void cacheAllThreads(Context context) {
116609a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang        if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
116709a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang            LogTag.debug("[Conversation] cacheAllThreads: begin");
1168627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1169627007213deb59ef938c80353c8f3598b01478b3Wei Huang        synchronized (Cache.getInstance()) {
11706bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            if (sLoadingThreads) {
1171627007213deb59ef938c80353c8f3598b01478b3Wei Huang                return;
1172627007213deb59ef938c80353c8f3598b01478b3Wei Huang                }
11736bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor            sLoadingThreads = true;
1174627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1175627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1176627007213deb59ef938c80353c8f3598b01478b3Wei Huang        // Keep track of what threads are now on disk so we
1177627007213deb59ef938c80353c8f3598b01478b3Wei Huang        // can discard anything removed from the cache.
1178627007213deb59ef938c80353c8f3598b01478b3Wei Huang        HashSet<Long> threadsOnDisk = new HashSet<Long>();
1179627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1180627007213deb59ef938c80353c8f3598b01478b3Wei Huang        // Query for all conversations.
1181627007213deb59ef938c80353c8f3598b01478b3Wei Huang        Cursor c = context.getContentResolver().query(sAllThreadsUri,
1182627007213deb59ef938c80353c8f3598b01478b3Wei Huang                ALL_THREADS_PROJECTION, null, null, null);
1183627007213deb59ef938c80353c8f3598b01478b3Wei Huang        try {
1184627007213deb59ef938c80353c8f3598b01478b3Wei Huang            if (c != null) {
1185627007213deb59ef938c80353c8f3598b01478b3Wei Huang                while (c.moveToNext()) {
1186627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    long threadId = c.getLong(ID);
1187627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    threadsOnDisk.add(threadId);
1188627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1189627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    // Try to find this thread ID in the cache.
1190627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    Conversation conv;
1191627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    synchronized (Cache.getInstance()) {
1192627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        conv = Cache.get(threadId);
1193627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    }
1194627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1195627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    if (conv == null) {
1196627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        // Make a new Conversation and put it in
1197627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        // the cache if necessary.
1198627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        conv = new Conversation(context, c, true);
1199627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        try {
1200627007213deb59ef938c80353c8f3598b01478b3Wei Huang                            synchronized (Cache.getInstance()) {
1201627007213deb59ef938c80353c8f3598b01478b3Wei Huang                                Cache.put(conv);
1202627007213deb59ef938c80353c8f3598b01478b3Wei Huang                            }
1203627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        } catch (IllegalStateException e) {
120410ca1d3969305df50fb07a17f5d23b0ed59f7868Tom Taylor                            LogTag.error("Tried to add duplicate Conversation to Cache" +
1205c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                                    " for threadId: " + threadId + " new conv: " + conv);
1206c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                            if (!Cache.replace(conv)) {
1207c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                                LogTag.error("cacheAllThreads cache.replace failed on " + conv);
1208c1342c003f027f564b44b4f4f93d6a6e780aa1c7Tom Taylor                            }
1209627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        }
1210627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    } else {
1211627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        // Or update in place so people with references
1212627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        // to conversations get updated too.
1213627007213deb59ef938c80353c8f3598b01478b3Wei Huang                        fillFromCursor(context, conv, c, true);
1214627007213deb59ef938c80353c8f3598b01478b3Wei Huang                    }
1215627007213deb59ef938c80353c8f3598b01478b3Wei Huang                }
1216627007213deb59ef938c80353c8f3598b01478b3Wei Huang            }
1217627007213deb59ef938c80353c8f3598b01478b3Wei Huang        } finally {
1218627007213deb59ef938c80353c8f3598b01478b3Wei Huang            if (c != null) {
1219627007213deb59ef938c80353c8f3598b01478b3Wei Huang                c.close();
1220627007213deb59ef938c80353c8f3598b01478b3Wei Huang            }
1221627007213deb59ef938c80353c8f3598b01478b3Wei Huang            synchronized (Cache.getInstance()) {
12226bbfdd3cc9cbe6b31dc64f122f3308563d19e077Tom Taylor                sLoadingThreads = false;
1223627007213deb59ef938c80353c8f3598b01478b3Wei Huang            }
1224627007213deb59ef938c80353c8f3598b01478b3Wei Huang        }
1225627007213deb59ef938c80353c8f3598b01478b3Wei Huang
1226627007213deb59ef938c80353c8f3598b01478b3Wei Huang        // Purge the cache of threads that no longer exist on disk.
1227627007213deb59ef938c80353c8f3598b01478b3Wei Huang        Cache.keepOnly(threadsOnDisk);
122809a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang
122909a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang        if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
123009a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang            LogTag.debug("[Conversation] cacheAllThreads: finished");
123109a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang            Cache.dumpCache();
123209a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang        }
1233627007213deb59ef938c80353c8f3598b01478b3Wei Huang    }
123471bbae69cee0da902032743d0702e283cfe31504Ficus Kirkpatrick
1235e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor    private boolean loadFromThreadId(long threadId, boolean allowQuery) {
123670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        Cursor c = mContext.getContentResolver().query(sAllThreadsUri, ALL_THREADS_PROJECTION,
123770c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick                "_id=" + Long.toString(threadId), null, null);
123870c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        try {
123970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            if (c.moveToFirst()) {
1240e692b9dcf5742e7a3ef3a7e64b44bb9d08f05b57Tom Taylor                fillFromCursor(mContext, this, c, allowQuery);
12416bbfd941885c588cc5d94e345d2cfc4197134a19Tom Taylor
124209a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                if (threadId != mThreadId) {
124309a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                    LogTag.error("loadFromThreadId: fillFromCursor returned differnt thread_id!" +
124409a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                            " threadId=" + threadId + ", mThreadId=" + mThreadId);
124509a75ac1d3710e60dbe78ead3dee6863ffb380caWei Huang                }
124670c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            } else {
1247d62ef06699b3ca5048c5642bd50300e9a2eb04a1Tom Taylor                LogTag.error("loadFromThreadId: Can't find thread ID " + threadId);
12486cba8248f3e41921b03cc74a823a6347016e69baTom Taylor                return false;
124970c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            }
125070c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        } finally {
125170c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick            c.close();
125270c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick        }
12536cba8248f3e41921b03cc74a823a6347016e69baTom Taylor        return true;
125470c73e05a792832aa28da751cdaf3fa83a7b8113Ficus Kirkpatrick    }
12551149674a2a3c13c7b3b41e5057880e011a45b4b4Ben Dodson
12566d38b19aa9aef46afa855187f23e44d4c06f8878Tom Taylor    public static String getRecipients(Uri uri) {
12571149674a2a3c13c7b3b41e5057880e011a45b4b4Ben Dodson        String base = uri.getSchemeSpecificPart();
12581149674a2a3c13c7b3b41e5057880e011a45b4b4Ben Dodson        int pos = base.indexOf('?');
12591149674a2a3c13c7b3b41e5057880e011a45b4b4Ben Dodson        return (pos == -1) ? base : base.substring(0, pos);
12601149674a2a3c13c7b3b41e5057880e011a45b4b4Ben Dodson    }
1261b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor
1262b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    public static void dump() {
1263b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Cache.dumpCache();
1264b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    }
1265b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor
1266b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    public static void dumpThreadsTable(Context context) {
1267b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        LogTag.debug("**** Dump of threads table ****");
1268b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Cursor c = context.getContentResolver().query(sAllThreadsUri,
1269b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                ALL_THREADS_PROJECTION, null, null, "date ASC");
1270b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        try {
1271b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor            c.moveToPosition(-1);
1272b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor            while (c.moveToNext()) {
1273b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                String snippet = MessageUtils.extractEncStrFromCursor(c, SNIPPET, SNIPPET_CS);
12747a063cb79843369325645ce059ec0f4676e0ca83Tom Taylor                Log.d(TAG, "dumpThreadsTable threadId: " + c.getLong(ID) +
1275b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + ThreadsColumns.DATE + " : " + c.getLong(DATE) +
1276b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + ThreadsColumns.MESSAGE_COUNT + " : " + c.getInt(MESSAGE_COUNT) +
1277b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + ThreadsColumns.SNIPPET + " : " + snippet +
1278b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + ThreadsColumns.READ + " : " + c.getInt(READ) +
1279b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + ThreadsColumns.ERROR + " : " + c.getInt(ERROR) +
1280b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + ThreadsColumns.HAS_ATTACHMENT + " : " + c.getInt(HAS_ATTACHMENT) +
1281b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + ThreadsColumns.RECIPIENT_IDS + " : " + c.getString(RECIPIENT_IDS));
1282b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor
1283b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                ContactList recipients = ContactList.getByIds(c.getString(RECIPIENT_IDS), false);
12847a063cb79843369325645ce059ec0f4676e0ca83Tom Taylor                Log.d(TAG, "----recipients: " + recipients.serialize());
1285b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor            }
1286b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        } finally {
1287b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor            c.close();
1288b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        }
1289b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    }
1290b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor
1291b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final String[] SMS_PROJECTION = new String[] {
1292b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        BaseColumns._ID,
1293b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        // For SMS
1294b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.THREAD_ID,
1295b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.ADDRESS,
1296b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.BODY,
1297b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.DATE,
1298b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.READ,
1299b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.TYPE,
1300b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.STATUS,
1301b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.LOCKED,
1302b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Sms.ERROR_CODE,
1303b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    };
1304b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor
1305b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    // The indexes of the default columns which must be consistent
1306b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    // with above PROJECTION.
1307b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_ID                  = 0;
1308b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_THREAD_ID           = 1;
1309b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_ADDRESS         = 2;
1310b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_BODY            = 3;
1311b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_DATE            = 4;
1312b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_READ            = 5;
1313b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_TYPE            = 6;
1314b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_STATUS          = 7;
1315b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_LOCKED          = 8;
1316b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    static final int COLUMN_SMS_ERROR_CODE      = 9;
1317b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor
1318b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    public static void dumpSmsTable(Context context) {
1319b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        LogTag.debug("**** Dump of sms table ****");
1320b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        Cursor c = context.getContentResolver().query(Sms.CONTENT_URI,
1321d4e997a8f0e03688143d1a50b381c6f363a204caTom Taylor                SMS_PROJECTION, null, null, "_id DESC");
1322b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        try {
1323d4e997a8f0e03688143d1a50b381c6f363a204caTom Taylor            // Only dump the latest 20 messages
1324b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor            c.moveToPosition(-1);
1325d4e997a8f0e03688143d1a50b381c6f363a204caTom Taylor            while (c.moveToNext() && c.getPosition() < 20) {
1326b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                String body = c.getString(COLUMN_SMS_BODY);
1327b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                LogTag.debug("dumpSmsTable " + BaseColumns._ID + ": " + c.getLong(COLUMN_ID) +
1328b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + Sms.THREAD_ID + " : " + c.getLong(DATE) +
1329b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + Sms.ADDRESS + " : " + c.getString(COLUMN_SMS_ADDRESS) +
1330d4e997a8f0e03688143d1a50b381c6f363a204caTom Taylor                        " " + Sms.BODY + " : " + body.substring(0, Math.min(body.length(), 8)) +
1331b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor                        " " + Sms.DATE + " : " + c.getLong(COLUMN_SMS_DATE) +
1332d4e997a8f0e03688143d1a50b381c6f363a204caTom Taylor                        " " + Sms.TYPE + " : " + c.getInt(COLUMN_SMS_TYPE));
1333b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor            }
1334b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        } finally {
1335b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor            c.close();
1336b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor        }
1337b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor    }
1338b3217a6ddcd9455968de7078bfbc0a901b4ff705Tom Taylor
1339b736686638eca62aa89cb15184711ef38413cb3eTom Taylor    /**
1340b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * verifySingleRecipient takes a threadId and a string recipient [phone number or email
1341b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * address]. It uses that threadId to lookup the row in the threads table and grab the
1342b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * recipient ids column. The recipient ids column contains a space-separated list of
1343b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * recipient ids. These ids are keys in the canonical_addresses table. The recipient is
1344b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * compared against what's stored in the mmssms.db, but only if the recipient id list has
1345b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * a single address.
1346b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * @param context is used for getting a ContentResolver
1347b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * @param threadId of the thread we're sending to
1348b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * @param recipientStr is a phone number or email address
1349b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     * @return the verified number or email of the recipient
1350b736686638eca62aa89cb15184711ef38413cb3eTom Taylor     */
1351b736686638eca62aa89cb15184711ef38413cb3eTom Taylor    public static String verifySingleRecipient(final Context context,
1352b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            final long threadId, final String recipientStr) {
1353b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        if (threadId <= 0) {
1354b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.error("verifySingleRecipient threadId is ZERO, recipient: " + recipientStr);
1355b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.dumpInternalTables(context);
1356b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            return recipientStr;
1357b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1358b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        Cursor c = context.getContentResolver().query(sAllThreadsUri, ALL_THREADS_PROJECTION,
1359b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                "_id=" + Long.toString(threadId), null, null);
1360b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        if (c == null) {
1361b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.error("verifySingleRecipient threadId: " + threadId +
1362b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                    " resulted in NULL cursor , recipient: " + recipientStr);
1363b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.dumpInternalTables(context);
1364b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            return recipientStr;
1365b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1366b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        String address = recipientStr;
1367b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        String recipientIds;
1368b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        try {
1369b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            if (!c.moveToFirst()) {
1370b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                LogTag.error("verifySingleRecipient threadId: " + threadId +
1371b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                        " can't moveToFirst , recipient: " + recipientStr);
1372b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                LogTag.dumpInternalTables(context);
1373b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                return recipientStr;
1374b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            }
1375b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            recipientIds = c.getString(RECIPIENT_IDS);
1376b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        } finally {
1377b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            c.close();
1378b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1379b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        String[] ids = recipientIds.split(" ");
1380b736686638eca62aa89cb15184711ef38413cb3eTom Taylor
1381b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        if (ids.length != 1) {
1382b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // We're only verifying the situation where we have a single recipient input against
1383b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // a thread with a single recipient. If the thread has multiple recipients, just
1384b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // assume the input number is correct and return it.
1385b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            return recipientStr;
1386b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1387b736686638eca62aa89cb15184711ef38413cb3eTom Taylor
1388b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        // Get the actual number from the canonical_addresses table for this recipientId
1389b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        address = RecipientIdCache.getSingleAddressFromCanonicalAddressInDb(context, ids[0]);
1390b736686638eca62aa89cb15184711ef38413cb3eTom Taylor
1391b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        if (TextUtils.isEmpty(address)) {
1392b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.error("verifySingleRecipient threadId: " + threadId +
1393b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                    " getSingleNumberFromCanonicalAddresses returned empty number for: " +
1394b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                    ids[0] + " recipientIds: " + recipientIds);
1395b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.dumpInternalTables(context);
1396b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            return recipientStr;
1397b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1398b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        if (PhoneNumberUtils.compareLoosely(recipientStr, address)) {
1399b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // Bingo, we've got a match. We're returning the input number because of area
1400b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // codes. We could have a number in the canonical_address name of "232-1012" and
1401b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // assume the user's phone's area code is 650. If the user sends a message to
1402b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // "(415) 232-1012", it will loosely match "232-1202". If we returned the value
1403b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // from the table (232-1012), the message would go to the wrong person (to the
1404b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            // person in the 650 area code rather than in the 415 area code).
1405b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            return recipientStr;
1406b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1407b736686638eca62aa89cb15184711ef38413cb3eTom Taylor
1408b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        if (context instanceof Activity) {
1409b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.warnPossibleRecipientMismatch("verifySingleRecipient for threadId: " +
1410b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                    threadId + " original recipient: " + recipientStr +
1411b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                    " recipient from DB: " + address, (Activity)context);
1412b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1413b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        LogTag.dumpInternalTables(context);
1414b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
1415b736686638eca62aa89cb15184711ef38413cb3eTom Taylor            LogTag.debug("verifySingleRecipient for threadId: " +
1416b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                    threadId + " original recipient: " + recipientStr +
1417b736686638eca62aa89cb15184711ef38413cb3eTom Taylor                    " recipient from DB: " + address);
1418b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        }
1419b736686638eca62aa89cb15184711ef38413cb3eTom Taylor        return address;
1420b736686638eca62aa89cb15184711ef38413cb3eTom Taylor    }
1421ab6141d9c98f1a6024fac52fe3c897076d8549c0Ficus Kirkpatrick}
1422