1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/*
2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project
3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License");
5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License.
6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at
7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *      http://www.apache.org/licenses/LICENSE-2.0
9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software
11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS,
12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and
14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License.
15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */
16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.datamodel;
18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.ContentResolver;
20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.ContentValues;
21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.database.Cursor;
22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.database.sqlite.SQLiteDoneException;
23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.database.sqlite.SQLiteStatement;
24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.net.Uri;
25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.ParcelFileDescriptor;
26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v4.util.ArrayMap;
27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v4.util.SimpleArrayMap;
28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.text.TextUtils;
29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.Factory;
31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns;
33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseHelper.PartColumns;
35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.ParticipantRefresh.ConversationParticipantsQuery;
37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.ConversationListItemData;
38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.MessageData;
39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.MessagePartData;
40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.ParticipantData;
41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.sms.MmsUtils;
42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.ui.UIIntents;
43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Assert;
44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Assert.DoesNotRunOnMainThread;
45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.AvatarUriUtil;
46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.ContentType;
47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.LogUtil;
48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.OsUtil;
49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.PhoneUtils;
50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.UriUtil;
51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.widget.WidgetConversationProvider;
52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.google.common.annotations.VisibleForTesting;
53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.io.IOException;
55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.ArrayList;
56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.HashSet;
57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.List;
58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport javax.annotation.Nullable;
59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/**
62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * This class manages updating our local database
63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */
64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class BugleDatabaseOperations {
65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Global cache of phone numbers -> participant id mapping since this call is expensive.
69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final ArrayMap<String, String> sNormalizedPhoneNumberToParticipantIdCache =
70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            new ArrayMap<String, String>();
71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Convert list of recipient strings (email/phone number) into list of ConversationParticipants
74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param recipients The recipient list
76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param refSubId The subId used to normalize phone numbers in the recipients
77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static ArrayList<ParticipantData> getConversationParticipantsFromRecipients(
79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final List<String> recipients, final int refSubId) {
80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Generate a list of partially formed participants
81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<ParticipantData> participants = new
82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                ArrayList<ParticipantData>();
83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (recipients != null) {
85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            for (final String recipient : recipients) {
86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                participants.add(ParticipantData.getFromRawPhoneBySimLocale(recipient, refSubId));
87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return participants;
90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Sanitize a given list of conversation participants by de-duping and stripping out self
94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * phone number in group conversation.
95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void sanitizeConversationParticipants(final List<ParticipantData> participants) {
98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (participants.size() > 0) {
100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // First remove redundant phone numbers
101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final HashSet<String> recipients = new HashSet<String>();
102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            for (int i = participants.size() - 1; i >= 0; i--) {
103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final String recipient = participants.get(i).getNormalizedDestination();
104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (!recipients.contains(recipient)) {
105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    recipients.add(recipient);
106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                } else {
107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    participants.remove(i);
108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (participants.size() > 1) {
111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Remove self phone number from group conversation.
112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final HashSet<String> selfNumbers =
113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        PhoneUtils.getDefault().getNormalizedSelfNumbers();
114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                int removed = 0;
115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Do this two-pass scan to avoid unnecessary memory allocation.
116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Prescan to count the self numbers in the list
117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                for (final ParticipantData p : participants) {
118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    if (selfNumbers.contains(p.getNormalizedDestination())) {
119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        removed++;
120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    }
121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // If all are self numbers, maybe that's what the user wants, just leave
123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // the participants as is. Otherwise, do another scan to remove self numbers.
124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (removed < participants.size()) {
125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    for (int i = participants.size() - 1; i >= 0; i--) {
126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        final String recipient = participants.get(i).getNormalizedDestination();
127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        if (selfNumbers.contains(recipient)) {
128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            participants.remove(i);
129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        }
130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    }
131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Convert list of ConversationParticipants into recipient strings (email/phone number)
138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static ArrayList<String> getRecipientsFromConversationParticipants(
141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final List<ParticipantData> participants) {
142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // First find the thread id for this list of participants.
144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<String> recipients = new ArrayList<String>();
145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final ParticipantData participant : participants) {
147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            recipients.add(participant.getSendDestination());
148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return recipients;
150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get or create a conversation based on the message's thread id
154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * NOTE: There are phones on which you can't get the recipients from the thread id for SMS
156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * until you have a message, so use getOrCreateConversationFromRecipient instead.
157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * TODO: Should this be in MMS/SMS code?
159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param db the database
161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param threadId The message's thread
162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param senderBlocked Flag whether sender of message is in blocked people list
163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param refSubId The reference subId for canonicalize phone numbers
164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return conversationId
165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getOrCreateConversationFromThreadId(final DatabaseWrapper db,
168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final long threadId, final boolean senderBlocked, final int refSubId) {
169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<ParticipantData> participants =
172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                getConversationParticipantsFromRecipients(recipients, refSubId);
173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return getOrCreateConversation(db, threadId, senderBlocked, participants, false, false,
175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                null);
176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get or create a conversation based on provided recipient
180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param db the database
182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param threadId The message's thread
183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param senderBlocked Flag whether sender of message is in blocked people list
184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param recipient recipient for thread
185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return conversationId
186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getOrCreateConversationFromRecipient(final DatabaseWrapper db,
189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final long threadId, final boolean senderBlocked, final ParticipantData recipient) {
190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<ParticipantData> recipients = new ArrayList<>(1);
192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        recipients.add(recipient);
193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return getOrCreateConversation(db, threadId, senderBlocked, recipients, false, false, null);
194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get or create a conversation based on provided participants
198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param db the database
200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param threadId The message's thread
201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param archived Flag whether the conversation should be created archived
202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param participants list of conversation participants
203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param noNotification If notification should be disabled
204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param noVibrate If vibrate on notification should be disabled
205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param soundUri If there is custom sound URI
206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return a conversation id
207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getOrCreateConversation(final DatabaseWrapper db, final long threadId,
210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean archived, final ArrayList<ParticipantData> participants,
211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            boolean noNotification, boolean noVibrate, String soundUri) {
212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Check to see if this conversation is already in out local db cache
215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String conversationId = BugleDatabaseOperations.getExistingConversation(db, threadId,
216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                false);
217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (conversationId == null) {
219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationName = ConversationListItemData.generateConversationName(
220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    participants);
221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Create the conversation with the default self participant which always maps to
223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // the system default subscription.
224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final ParticipantData self = ParticipantData.getSelfParticipant(
225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ParticipantData.DEFAULT_SELF_SUB_ID);
226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            db.beginTransaction();
228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            try {
229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Look up the "self" participantId (creating if necessary)
230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final String selfId =
231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Create a new conversation
233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                conversationId = BugleDatabaseOperations.createConversationInTransaction(
234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        db, threadId, conversationName, selfId, participants, archived,
235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        noNotification, noVibrate, soundUri);
236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                db.setTransactionSuccessful();
237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } finally {
238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                db.endTransaction();
239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return conversationId;
243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get a conversation from the local DB based on the message's thread id.
247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper     The database
249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param threadId      The message's thread in the SMS database
250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param senderBlocked Flag whether sender of message is in blocked people list
251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return The existing conversation id or null
252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @VisibleForTesting
254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getExistingConversation(final DatabaseWrapper dbWrapper,
256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final long threadId, final boolean senderBlocked) {
257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String conversationId = null;
259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Look for an existing conversation in the db with this thread id
263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.rawQuery("SELECT " + ConversationColumns._ID
264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + " WHERE " + ConversationColumns.SMS_THREAD_ID + "=" + threadId,
266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    null);
267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                Assert.isTrue(cursor.getCount() == 1);
270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                conversationId = cursor.getString(0);
271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return conversationId;
279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get the thread id for an existing conversation from the local DB.
283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper The database
285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param conversationId The conversation to look up thread for
286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return The thread id. Returns -1 if the conversation was not found or if it was found
287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * but the thread column was NULL.
288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static long getThreadId(final DatabaseWrapper dbWrapper, final String conversationId) {
291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        long threadId = -1;
293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { ConversationColumns.SMS_THREAD_ID },
298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationColumns._ID + " =?",
299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { conversationId },
300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    null, null, null);
301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                Assert.isTrue(cursor.getCount() == 1);
304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (!cursor.isNull(0)) {
305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    threadId = cursor.getLong(0);
306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
307d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
308d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
309d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
310d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
311d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
312d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
313d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
314d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return threadId;
315d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
316d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
317d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
318d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static boolean isBlockedDestination(final DatabaseWrapper db, final String destination) {
319d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
320d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return isBlockedParticipant(db, destination, ParticipantColumns.NORMALIZED_DESTINATION);
321d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
322d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
323d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static boolean isBlockedParticipant(final DatabaseWrapper db, final String participantId) {
324d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return isBlockedParticipant(db, participantId, ParticipantColumns._ID);
325d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
326d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
327d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static boolean isBlockedParticipant(final DatabaseWrapper db, final String value,
328d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String column) {
329d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
330d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
331d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
332d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { ParticipantColumns.BLOCKED },
333d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    column + "=? AND " + ParticipantColumns.SUB_ID + "=?",
334d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { value,
335d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    Integer.toString(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
336d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    null, null, null);
337d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
338d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
339d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
340d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return cursor.getInt(0) == 1;
341d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
342d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
343d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
344d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
345d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
346d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
347d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return false;  // if there's no row, it's not blocked :-)
348d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
349d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
350d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
351d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Create a conversation in the local DB based on the message's thread id.
352d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
353d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * It's up to the caller to make sure that this is all inside a transaction.  It will return
354d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * null if it's not in the local DB.
355d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
356d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper     The database
357d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param threadId      The message's thread
358d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param selfId        The selfId to make default for this conversation
359d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param archived      Flag whether the conversation should be created archived
360d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param noNotification If notification should be disabled
361d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param noVibrate     If vibrate on notification should be disabled
362d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param soundUri      The customized sound
363d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return The existing conversation id or new conversation id
364d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
365d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static String createConversationInTransaction(final DatabaseWrapper dbWrapper,
366d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final long threadId, final String conversationName, final String selfId,
367d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final List<ParticipantData> participants, final boolean archived,
368d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            boolean noNotification, boolean noVibrate, String soundUri) {
369d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // We want conversation and participant creation to be atomic
370d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
371d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        boolean hasEmailAddress = false;
372d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final ParticipantData participant : participants) {
373d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.isTrue(!participant.isSelf());
374d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (participant.isEmail()) {
375d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                hasEmailAddress = true;
376d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
377d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
378d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
379d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // TODO : Conversations state - normal vs. archived
380d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
381d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Insert a new local conversation for this thread id
382d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
383d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.SMS_THREAD_ID, threadId);
384d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Start with conversation hidden - sending a message or saving a draft will change that
385d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.SORT_TIMESTAMP, 0L);
386d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.CURRENT_SELF_ID, selfId);
387d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.PARTICIPANT_COUNT, participants.size());
388d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.INCLUDE_EMAIL_ADDRESS, (hasEmailAddress ? 1 : 0));
389d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (archived) {
390d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.ARCHIVE_STATUS, 1);
391d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
392d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (noNotification) {
393d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.NOTIFICATION_ENABLED, 0);
394d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
395d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (noVibrate) {
396d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.NOTIFICATION_VIBRATION, 0);
397d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
398d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!TextUtils.isEmpty(soundUri)) {
399d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.NOTIFICATION_SOUND_URI, soundUri);
400d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
401d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
402d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        fillParticipantData(values, participants);
403d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
404d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final long conversationRowId = dbWrapper.insert(DatabaseHelper.CONVERSATIONS_TABLE, null,
405d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                values);
406d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
407d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(conversationRowId != -1);
408d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (conversationRowId == -1) {
409d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            LogUtil.e(TAG, "BugleDatabaseOperations : failed to insert conversation into table");
410d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return null;
411d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
412d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
413d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String conversationId = Long.toString(conversationRowId);
414d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
415d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Make sure that participants are added for this conversation
416d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final ParticipantData participant : participants) {
417d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // TODO: Use blocking information
418d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            addParticipantToConversation(dbWrapper, participant, conversationId);
419d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
420d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
421d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Now fully resolved participants available can update conversation name / avatar.
422d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // b/16437575: We cannot use the participants directly, but instead have to call
423d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // getParticipantsForConversation() to retrieve the actual participants. This is needed
424d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // because the call to addParticipantToConversation() won't fill up the ParticipantData
425d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // if the participant already exists in the participant table. For example, say you have
426d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // an existing conversation with John. Now if you create a new group conversation with
427d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Jeff & John with only their phone numbers, then when we try to add John's number to the
428d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // group conversation, we see that he's already in the participant table, therefore we
429d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // short-circuit any steps to actually fill out the ParticipantData for John other than
430d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // just returning his participant id. Eventually, the ParticipantData we have is still the
431d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // raw data with just the phone number. getParticipantsForConversation(), on the other
432d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // hand, will fill out all the info for each participant from the participants table.
433d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId,
434d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                getParticipantsForConversation(dbWrapper, conversationId));
435d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
436d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return conversationId;
437d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
438d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
439d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static void fillParticipantData(final ContentValues values,
440d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final List<ParticipantData> participants) {
441d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (participants != null && !participants.isEmpty()) {
442d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final Uri avatarUri = AvatarUriUtil.createAvatarUri(participants);
443d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.ICON, avatarUri.toString());
444d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
445d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            long contactId;
446d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            String lookupKey;
447d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            String destination;
448d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (participants.size() == 1) {
449d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final ParticipantData firstParticipant = participants.get(0);
450d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                contactId = firstParticipant.getContactId();
451d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                lookupKey = firstParticipant.getLookupKey();
452d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                destination = firstParticipant.getNormalizedDestination();
453d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
454d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                contactId = 0;
455d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                lookupKey = null;
456d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                destination = null;
457d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
458d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
459d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.PARTICIPANT_CONTACT_ID, contactId);
460d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.PARTICIPANT_LOOKUP_KEY, lookupKey);
461d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION, destination);
462d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
463d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
464d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
465d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
466d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Delete conversation and associated messages/parts
467d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
468d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
469d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static boolean deleteConversation(final DatabaseWrapper dbWrapper,
470d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final long cutoffTimestamp) {
471d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
472d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dbWrapper.beginTransaction();
473d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        boolean conversationDeleted = false;
474d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        boolean conversationMessagesDeleted = false;
475d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
476d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Delete existing messages
477d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cutoffTimestamp == Long.MAX_VALUE) {
478d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Delete parts and messages
479d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
480d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId });
481d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                conversationMessagesDeleted = true;
482d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
483d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Delete all messages prior to the cutoff
484d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
485d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        MessageColumns.CONVERSATION_ID + "=? AND "
486d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                + MessageColumns.RECEIVED_TIMESTAMP + "<=?",
487d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                new String[] { conversationId, Long.toString(cutoffTimestamp) });
488d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
489d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Delete any draft message. The delete above may not always include the draft,
490d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // because under certain scenarios (e.g. sending messages in progress), the draft
491d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // timestamp can be larger than the cutoff time, which is generally the conversation
492d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // sort timestamp. Because of how the sms/mms provider works on some newer
493d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // devices, it's important that we never delete all the messages in a conversation
494d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // without also deleting the conversation itself (see b/20262204 for details).
495d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
496d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
497d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        new String[] {
498d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
499d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            conversationId
500d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        });
501d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
502d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Check to see if there are any messages left in the conversation
503d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final long count = dbWrapper.queryNumEntries(DatabaseHelper.MESSAGES_TABLE,
504d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId });
505d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                conversationMessagesDeleted = (count == 0);
506d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
507d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Log detail information if there are still messages left in the conversation
508d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (!conversationMessagesDeleted) {
509d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    final long maxTimestamp =
510d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            getConversationMaxTimestamp(dbWrapper, conversationId);
511d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    LogUtil.w(TAG, "BugleDatabaseOperations:"
512d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + " cannot delete all messages in a conversation"
513d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + ", after deletion: count=" + count
514d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + ", max timestamp=" + maxTimestamp
515d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + ", cutoff timestamp=" + cutoffTimestamp);
516d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
517d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
518d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
519d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (conversationMessagesDeleted) {
520d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Delete conversation row
521d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final int count = dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE,
522d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ConversationColumns._ID + "=?", new String[] { conversationId });
523d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                conversationDeleted = (count > 0);
524d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
525d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.setTransactionSuccessful();
526d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
527d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.endTransaction();
528d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
529d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return conversationDeleted;
530d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
531d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
532d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final String MAX_RECEIVED_TIMESTAMP =
533d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            "MAX(" + MessageColumns.RECEIVED_TIMESTAMP + ")";
534d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
535d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get the max received timestamp of a conversation's messages
536d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
537d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static long getConversationMaxTimestamp(final DatabaseWrapper dbWrapper,
538d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId) {
539d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final Cursor cursor = dbWrapper.query(
540d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                DatabaseHelper.MESSAGES_TABLE,
541d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                new String[]{ MAX_RECEIVED_TIMESTAMP },
542d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                MessageColumns.CONVERSATION_ID + "=?",
543d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                new String[]{ conversationId },
544d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                null, null, null);
545d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (cursor != null) {
546d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            try {
547d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (cursor.moveToFirst()) {
548d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return cursor.getLong(0);
549d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
550d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } finally {
551d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
552d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
553d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
554d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return 0;
555d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
556d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
557d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
558d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateConversationMetadataInTransaction(final DatabaseWrapper dbWrapper,
559d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final String messageId, final long latestTimestamp,
560d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean keepArchived, final String smsServiceCenter,
561d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean shouldAutoSwitchSelfId) {
562d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
563d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
564d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
565d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
566d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.LATEST_MESSAGE_ID, messageId);
567d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.SORT_TIMESTAMP, latestTimestamp);
568d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!TextUtils.isEmpty(smsServiceCenter)) {
569d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.SMS_SERVICE_CENTER, smsServiceCenter);
570d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
571d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
572d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // When the conversation gets updated with new messages, unarchive the conversation unless
573d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // the sender is blocked, or we have been told to keep it archived.
574d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!keepArchived) {
575d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.ARCHIVE_STATUS, 0);
576d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
577d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
578d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final MessageData message = readMessage(dbWrapper, messageId);
579d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        addSnippetTextAndPreviewToContentValues(message, false /* showDraft */, values);
580d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
581d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (shouldAutoSwitchSelfId) {
582d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            addSelfIdAutoSwitchInfoToContentValues(dbWrapper, message, conversationId, values);
583d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
584d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
585d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Conversation always exists as this method is called from ActionService only after
586d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // reading and if necessary creating the conversation.
587d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateConversationRow(dbWrapper, conversationId, values);
588d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
589d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (shouldAutoSwitchSelfId && OsUtil.isAtLeastL_MR1()) {
590d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Normally, the draft message compose UI trusts its UI state for providing up-to-date
591d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // conversation self id. Therefore, notify UI through local broadcast receiver about
592d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // this external change so the change can be properly reflected.
593d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            UIIntents.get().broadcastConversationSelfIdChange(dbWrapper.getContext(),
594d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    conversationId, getConversationSelfId(dbWrapper, conversationId));
595d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
596d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
597d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
598d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
599d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateConversationMetadataInTransaction(final DatabaseWrapper db,
600d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final String messageId, final long latestTimestamp,
601d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean keepArchived, final boolean shouldAutoSwitchSelfId) {
602d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
603d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateConversationMetadataInTransaction(
604d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                db, conversationId, messageId, latestTimestamp, keepArchived, null,
605d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                shouldAutoSwitchSelfId);
606d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
607d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
608d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
609d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateConversationArchiveStatusInTransaction(final DatabaseWrapper dbWrapper,
610d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final boolean isArchived) {
611d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
612d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
613d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
614d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.ARCHIVE_STATUS, isArchived ? 1 : 0);
615d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateConversationRowIfExists(dbWrapper, conversationId, values);
616d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
617d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
618d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static void addSnippetTextAndPreviewToContentValues(final MessageData message,
619d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean showDraft, final ContentValues values) {
620d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0);
621d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.SNIPPET_TEXT, message.getMessageText());
622d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.SUBJECT_TEXT, message.getMmsSubject());
623d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
624d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String type = null;
625d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String uriString = null;
626d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final MessagePartData part : message.getParts()) {
627d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (part.isAttachment() &&
628d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ContentType.isConversationListPreviewableType(part.getContentType())) {
629d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                uriString = part.getContentUri().toString();
630d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                type = part.getContentType();
631d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                break;
632d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
633d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
634d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.PREVIEW_CONTENT_TYPE, type);
635d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.PREVIEW_URI, uriString);
636d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
637d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
638d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
639d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Adds self-id auto switch info for a conversation if the last message has a different
640d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * subscription than the conversation's.
641d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return true if self id will need to be changed, false otherwise.
642d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
643d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static boolean addSelfIdAutoSwitchInfoToContentValues(final DatabaseWrapper dbWrapper,
644d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessageData message, final String conversationId, final ContentValues values) {
645d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Only auto switch conversation self for incoming messages.
646d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!OsUtil.isAtLeastL_MR1() || !message.getIsIncoming()) {
647d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false;
648d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
649d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
650d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String conversationSelfId = getConversationSelfId(dbWrapper, conversationId);
651d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String messageSelfId = message.getSelfId();
652d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
653d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (conversationSelfId == null || messageSelfId == null) {
654d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false;
655d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
656d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
657d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Get the sub IDs in effect for both the message and the conversation and compare them:
658d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // 1. If message is unbound (using default sub id), then the message was sent with
659d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        //    pre-MSIM support. Don't auto-switch because we don't know the subscription for the
660d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        //    message.
661d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // 2. If message is bound,
662d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        //    i. If conversation is unbound, use the system default sub id as its effective sub.
663d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        //    ii. If conversation is bound, use its subscription directly.
664d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        //    Compare the message sub id with the conversation's effective sub id. If they are
665d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        //    different, auto-switch the conversation to the message's sub.
666d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ParticipantData conversationSelf = getExistingParticipant(dbWrapper,
667d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                conversationSelfId);
668d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ParticipantData messageSelf = getExistingParticipant(dbWrapper, messageSelfId);
669d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!messageSelf.isActiveSubscription()) {
670d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Don't switch if the message subscription is no longer active.
671d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false;
672d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
673d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int messageSubId = messageSelf.getSubId();
674d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (messageSubId == ParticipantData.DEFAULT_SELF_SUB_ID) {
675d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false;
676d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
677d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
678d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int conversationEffectiveSubId =
679d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                PhoneUtils.getDefault().getEffectiveSubId(conversationSelf.getSubId());
680d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
681d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (conversationEffectiveSubId != messageSubId) {
682d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return addConversationSelfIdToContentValues(dbWrapper, messageSelf.getId(), values);
683d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
684d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return false;
685d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
686d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
687d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
688d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Adds conversation self id updates to ContentValues given. This performs check on the selfId
689d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * to ensure it's valid and active.
690d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return true if self id will need to be changed, false otherwise.
691d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
692d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static boolean addConversationSelfIdToContentValues(final DatabaseWrapper dbWrapper,
693d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String selfId, final ContentValues values) {
694d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Make sure the selfId passed in is valid and active.
695d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String selection = ParticipantColumns._ID + "=? AND " +
696d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                ParticipantColumns.SIM_SLOT_ID + "<>?";
697d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
698d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
699d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
700d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { ParticipantColumns._ID }, selection,
701d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { selfId, String.valueOf(ParticipantData.INVALID_SLOT_ID) },
702d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    null, null, null);
703d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
704d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null && cursor.getCount() > 0) {
705d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                values.put(ConversationColumns.CURRENT_SELF_ID, selfId);
706d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return true;
707d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
708d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
709d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
710d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
711d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
712d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
713d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return false;
714d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
715d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
716d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static void updateConversationDraftSnippetAndPreviewInTransaction(
717d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper dbWrapper, final String conversationId,
718d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessageData draftMessage) {
719d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
720d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
721d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        long sortTimestamp = 0L;
722d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
723d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
724d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Check to find the latest message in the conversation
725d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
726d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    REFRESH_CONVERSATION_MESSAGE_PROJECTION,
727d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.CONVERSATION_ID + "=?",
728d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[]{conversationId}, null, null,
729d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
730d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
731d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
732d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                sortTimestamp = cursor.getLong(1);
733d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
734d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
735d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
736d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
737d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
738d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
739d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
740d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
741d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
742d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (draftMessage == null || !draftMessage.hasContent()) {
743d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.SHOW_DRAFT, 0);
744d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, "");
745d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, "");
746d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, "");
747d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_PREVIEW_URI, "");
748d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
749d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sortTimestamp = Math.max(sortTimestamp, draftMessage.getReceivedTimeStamp());
750d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.SHOW_DRAFT, 1);
751d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, draftMessage.getMessageText());
752d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, draftMessage.getMmsSubject());
753d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            String type = null;
754d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            String uriString = null;
755d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            for (final MessagePartData part : draftMessage.getParts()) {
756d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (part.isAttachment() &&
757d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ContentType.isConversationListPreviewableType(part.getContentType())) {
758d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    uriString = part.getContentUri().toString();
759d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    type = part.getContentType();
760d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    break;
761d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
762d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
763d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, type);
764d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.put(ConversationColumns.DRAFT_PREVIEW_URI, uriString);
765d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
766d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.SORT_TIMESTAMP, sortTimestamp);
767d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Called in transaction after reading conversation row
768d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateConversationRow(dbWrapper, conversationId, values);
769d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
770d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
771d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
772d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static boolean updateConversationRowIfExists(final DatabaseWrapper dbWrapper,
773d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final ContentValues values) {
774d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
775d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return updateRowIfExists(dbWrapper, DatabaseHelper.CONVERSATIONS_TABLE,
776d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                ConversationColumns._ID, conversationId, values);
777d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
778d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
779d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
780d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateConversationRow(final DatabaseWrapper dbWrapper,
781d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final ContentValues values) {
782d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
783d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final boolean exists = updateConversationRowIfExists(dbWrapper, conversationId, values);
784d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(exists);
785d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
786d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
787d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
788d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static boolean updateMessageRowIfExists(final DatabaseWrapper dbWrapper,
789d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String messageId, final ContentValues values) {
790d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
791d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return updateRowIfExists(dbWrapper, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID,
792d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                messageId, values);
793d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
794d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
795d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
796d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateMessageRow(final DatabaseWrapper dbWrapper,
797d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String messageId, final ContentValues values) {
798d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
799d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final boolean exists = updateMessageRowIfExists(dbWrapper, messageId, values);
800d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(exists);
801d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
802d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
803d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
804d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static boolean updatePartRowIfExists(final DatabaseWrapper dbWrapper,
805d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String partId, final ContentValues values) {
806d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
807d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return updateRowIfExists(dbWrapper, DatabaseHelper.PARTS_TABLE, PartColumns._ID,
808d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                partId, values);
809d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
810d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
811d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
812d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Returns the default conversation name based on its participants.
813d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
814d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static String getDefaultConversationName(final List<ParticipantData> participants) {
815d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return ConversationListItemData.generateConversationName(participants);
816d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
817d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
818d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
819d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Updates a given conversation's name based on its participants.
820d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
821d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
822d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateConversationNameAndAvatarInTransaction(
823d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper dbWrapper, final String conversationId) {
824d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
825d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
826d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
827d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<ParticipantData> participants =
828d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                getParticipantsForConversation(dbWrapper, conversationId);
829d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId, participants);
830d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
831d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
832d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
833d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Updates a given conversation's name based on its participants.
834d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
835d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static void updateConversationNameAndAvatarInTransaction(
836d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper dbWrapper, final String conversationId,
837d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final List<ParticipantData> participants) {
838d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
839d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
840d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
841d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationColumns.NAME,
842d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                getDefaultConversationName(participants));
843d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
8447ad7ac27f12be44659f2f0ff112a7f8433ebb1b5Tony Mak        // Fill in IS_ENTERPRISE.
8457ad7ac27f12be44659f2f0ff112a7f8433ebb1b5Tony Mak        final boolean hasAnyEnterpriseContact =
8467ad7ac27f12be44659f2f0ff112a7f8433ebb1b5Tony Mak                ConversationListItemData.hasAnyEnterpriseContact(participants);
8477ad7ac27f12be44659f2f0ff112a7f8433ebb1b5Tony Mak        values.put(ConversationColumns.IS_ENTERPRISE, hasAnyEnterpriseContact);
8487ad7ac27f12be44659f2f0ff112a7f8433ebb1b5Tony Mak
849d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        fillParticipantData(values, participants);
850d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
851d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Used by background thread when refreshing conversation so conversation could be deleted.
852d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateConversationRowIfExists(dbWrapper, conversationId, values);
853d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
854d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        WidgetConversationProvider.notifyConversationRenamed(Factory.get().getApplicationContext(),
855d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                conversationId);
856d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
857d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
858d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
859d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Updates a given conversation's self id.
860d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
861d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
862d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateConversationSelfIdInTransaction(
863d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper dbWrapper, final String conversationId, final String selfId) {
864d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
865d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
866d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
867d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (addConversationSelfIdToContentValues(dbWrapper, selfId, values)) {
868d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            updateConversationRowIfExists(dbWrapper, conversationId, values);
869d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
870d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
871d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
872d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
873d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getConversationSelfId(final DatabaseWrapper dbWrapper,
874d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId) {
875d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
876d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
877d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
878d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
879d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { ConversationColumns.CURRENT_SELF_ID },
880d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationColumns._ID + "=?",
881d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { conversationId },
882d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    null, null, null);
883d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
884d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
885d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return cursor.getString(0);
886d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
887d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
888d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
889d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
890d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
891d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
892d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return null;
893d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
894d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
895d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
896d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Frees up memory associated with phone number to participant id matching.
897d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
898d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
899d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void clearParticipantIdCache() {
900d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
901d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
902d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sNormalizedPhoneNumberToParticipantIdCache.clear();
903d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
904d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
905d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
906d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
907d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static ArrayList<String> getRecipientsForConversation(final DatabaseWrapper dbWrapper,
908d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId) {
909d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
910d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<ParticipantData> participants =
911d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                getParticipantsForConversation(dbWrapper, conversationId);
912d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
913d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<String> recipients = new ArrayList<String>();
914d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final ParticipantData participant : participants) {
915d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            recipients.add(participant.getSendDestination());
916d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
917d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
918d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return recipients;
919d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
920d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
921d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
922d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getSmsServiceCenterForConversation(final DatabaseWrapper dbWrapper,
923d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId) {
924d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
925d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
926d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
927d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
928d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { ConversationColumns.SMS_SERVICE_CENTER },
929d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationColumns._ID + "=?",
930d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { conversationId },
931d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    null, null, null);
932d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
933d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
934d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return cursor.getString(0);
935d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
936d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
937d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
938d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
939d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
940d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
941d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return null;
942d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
943d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
944d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
945d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static ParticipantData getExistingParticipant(final DatabaseWrapper dbWrapper,
946d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String participantId) {
947d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
948d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        ParticipantData participant = null;
949d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
950d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
951d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
952d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ParticipantData.ParticipantsQuery.PROJECTION,
953d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ParticipantColumns._ID + " =?",
954d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { participantId }, null, null, null);
955d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
956d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
957d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                participant = ParticipantData.getFromCursor(cursor);
958d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
959d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
960d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
961d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
962d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
963d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
964d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
965d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return participant;
966d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
967d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
968d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static int getSelfSubscriptionId(final DatabaseWrapper dbWrapper,
969d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String selfParticipantId) {
970d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ParticipantData selfParticipant = BugleDatabaseOperations.getExistingParticipant(
971d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                dbWrapper, selfParticipantId);
972d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (selfParticipant != null) {
973d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.isTrue(selfParticipant.isSelf());
974d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return selfParticipant.getSubId();
975d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
976d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return ParticipantData.DEFAULT_SELF_SUB_ID;
977d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
978d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
979d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @VisibleForTesting
980d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
981d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static ArrayList<ParticipantData> getParticipantsForConversation(
982d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper dbWrapper, final String conversationId) {
983d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
984d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<ParticipantData> participants =
985d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                new ArrayList<ParticipantData>();
986d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
987d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
988d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
989d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ParticipantData.ParticipantsQuery.PROJECTION,
990d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ParticipantColumns._ID + " IN ( " + "SELECT "
991d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + ConversationParticipantsColumns.PARTICIPANT_ID + " AS "
992d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + ParticipantColumns._ID
993d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + " FROM " + DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE
994d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            + " WHERE " + ConversationParticipantsColumns.CONVERSATION_ID + " =? )",
995d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            new String[] { conversationId }, null, null, null);
996d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
997d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            while (cursor.moveToNext()) {
998d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                participants.add(ParticipantData.getFromCursor(cursor));
999d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1000d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1001d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1002d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1003d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1004d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1005d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1006d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return participants;
1007d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1008d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1009d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1010d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static MessageData readMessage(final DatabaseWrapper dbWrapper, final String messageId) {
1011d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1012d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final MessageData message = readMessageData(dbWrapper, messageId);
1013d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (message != null) {
1014d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            readMessagePartsData(dbWrapper, message, false);
1015d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1016d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return message;
1017d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1018d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1019d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @VisibleForTesting
1020d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static MessagePartData readMessagePartData(final DatabaseWrapper dbWrapper,
1021d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String partId) {
1022d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessagePartData messagePartData = null;
1023d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1024d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1025d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE,
1026d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessagePartData.getProjection(), PartColumns._ID + "=?",
1027d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { partId }, null, null, null);
1028d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
1029d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
1030d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                messagePartData = MessagePartData.createFromCursor(cursor);
1031d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1032d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1033d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1034d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1035d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1036d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1037d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return messagePartData;
1038d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1039d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1040d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1041d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static MessageData readMessageData(final DatabaseWrapper dbWrapper,
1042d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final Uri smsMessageUri) {
1043d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1044d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessageData message = null;
1045d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1046d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1047d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
1048d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageData.getProjection(), MessageColumns.SMS_MESSAGE_URI + "=?",
1049d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { smsMessageUri.toString() }, null, null, null);
1050d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
1051d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
1052d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                message = new MessageData();
1053d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                message.bind(cursor);
1054d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1055d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1056d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1057d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1058d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1059d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1060d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return message;
1061d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1062d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1063d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1064d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static MessageData readMessageData(final DatabaseWrapper dbWrapper,
1065d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String messageId) {
1066d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1067d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessageData message = null;
1068d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1069d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1070d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
1071d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageData.getProjection(), MessageColumns._ID + "=?",
1072d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { messageId }, null, null, null);
1073d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
1074d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
1075d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                message = new MessageData();
1076d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                message.bind(cursor);
1077d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1078d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1079d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1080d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1081d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1082d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1083d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return message;
1084d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1085d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1086d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1087d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Read all the parts for a message
1088d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper database
1089d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param message read parts for this message
1090d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param checkAttachmentFilesExist check each attachment file and only include if file exists
1091d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1092d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static void readMessagePartsData(final DatabaseWrapper dbWrapper,
1093d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessageData message, final boolean checkAttachmentFilesExist) {
1094d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentResolver contentResolver =
1095d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                Factory.get().getApplicationContext().getContentResolver();
1096d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1097d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1098d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE,
1099d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessagePartData.getProjection(), PartColumns.MESSAGE_ID + "=?",
1100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { message.getMessageId() }, null, null, null);
1101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            while (cursor.moveToNext()) {
1102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final MessagePartData messagePartData = MessagePartData.createFromCursor(cursor);
1103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (checkAttachmentFilesExist && messagePartData.isAttachment() &&
1104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        !UriUtil.isBugleAppResource(messagePartData.getContentUri())) {
1105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    try {
1106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        // Test that the file exists before adding the attachment to the draft
1107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        final ParcelFileDescriptor fileDescriptor =
1108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                contentResolver.openFileDescriptor(
1109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                        messagePartData.getContentUri(), "r");
1110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        if (fileDescriptor != null) {
1111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            fileDescriptor.close();
1112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            message.addPart(messagePartData);
1113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        }
1114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    } catch (final IOException e) {
1115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        // The attachment's temp storage no longer exists, just ignore the file
1116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    } catch (final SecurityException e) {
1117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        // Likely thrown by openFileDescriptor due to an expired access grant.
1118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
1119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            LogUtil.d(LogUtil.BUGLE_TAG, "uri: " + messagePartData.getContentUri());
1120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        }
1121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    }
1122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                } else {
1123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    message.addPart(messagePartData);
1124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Write a message part to our local database
1135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
1136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper     The database
1137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param messagePart   The message part to insert
1138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return The row id of the newly inserted part
1139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static String insertNewMessagePartInTransaction(final DatabaseWrapper dbWrapper,
1141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessagePartData messagePart, final String conversationId) {
1142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
1143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(!TextUtils.isEmpty(messagePart.getMessageId()));
1144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Insert a new part row
1146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final SQLiteStatement insert = messagePart.getInsertStatement(dbWrapper, conversationId);
1147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final long rowNumber = insert.executeInsert();
1148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.inRange(rowNumber, 0, Long.MAX_VALUE);
1150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String partId = Long.toString(rowNumber);
1151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Update the part id
1153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        messagePart.updatePartId(partId);
1154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return partId;
1156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Insert a message and its parts into the table
1160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void insertNewMessageInTransaction(final DatabaseWrapper dbWrapper,
1163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessageData message) {
1164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
1166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Insert message row
1168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final SQLiteStatement insert = message.getInsertStatement(dbWrapper);
1169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final long rowNumber = insert.executeInsert();
1170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.inRange(rowNumber, 0, Long.MAX_VALUE);
1172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String messageId = Long.toString(rowNumber);
1173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        message.updateMessageId(messageId);
1174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        //  Insert new parts
1175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final MessagePartData messagePart : message.getParts()) {
1176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            messagePart.updateMessageId(messageId);
1177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            insertNewMessagePartInTransaction(dbWrapper, messagePart, message.getConversationId());
1178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Update a message and add its parts into the table
1183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateMessageInTransaction(final DatabaseWrapper dbWrapper,
1186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessageData message) {
1187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
1189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String messageId = message.getMessageId();
1190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Check message still exists (sms sync or delete might have purged it)
1191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final MessageData current = BugleDatabaseOperations.readMessage(dbWrapper, messageId);
1192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (current != null) {
1193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Delete existing message parts)
1194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            deletePartsForMessage(dbWrapper, message.getMessageId());
1195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            //  Insert new parts
1196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            for (final MessagePartData messagePart : message.getParts()) {
1197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                messagePart.updatePartId(null);
1198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                messagePart.updateMessageId(message.getMessageId());
1199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                insertNewMessagePartInTransaction(dbWrapper, messagePart,
1200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        message.getConversationId());
1201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            //  Update message row
1203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final ContentValues values = new ContentValues();
1204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            message.populate(values);
1205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            updateMessageRowIfExists(dbWrapper, message.getMessageId(), values);
1206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateMessageAndPartsInTransaction(final DatabaseWrapper dbWrapper,
1211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessageData message, final List<MessagePartData> partsToUpdate) {
1212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
1214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
1215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final MessagePartData messagePart : partsToUpdate) {
1216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            values.clear();
1217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            messagePart.populate(values);
1218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            updatePartRowIfExists(dbWrapper, messagePart.getPartId(), values);
1219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.clear();
1221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        message.populate(values);
1222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateMessageRowIfExists(dbWrapper, message.getMessageId(), values);
1223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Delete all parts for a message
1227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static void deletePartsForMessage(final DatabaseWrapper dbWrapper,
1229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String messageId) {
1230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int cnt = dbWrapper.delete(DatabaseHelper.PARTS_TABLE,
1231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                PartColumns.MESSAGE_ID + " =?",
1232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                new String[] { messageId });
1233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.inRange(cnt, 0, Integer.MAX_VALUE);
1234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Delete one message and update the conversation (if necessary).
1238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
1239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return number of rows deleted (should be 1 or 0).
1240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static int deleteMessage(final DatabaseWrapper dbWrapper, final String messageId) {
1243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dbWrapper.beginTransaction();
1245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Read message to find out which conversation it is in
1247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final MessageData message = BugleDatabaseOperations.readMessage(dbWrapper, messageId);
1248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            int count = 0;
1250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (message != null) {
1251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final String conversationId = message.getConversationId();
1252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Delete message
1253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                count = dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
1254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        MessageColumns._ID + "=?", new String[] { messageId });
1255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (!deleteConversationIfEmptyInTransaction(dbWrapper, conversationId)) {
1257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    // TODO: Should we leave the conversation sort timestamp alone?
1258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    refreshConversationMetadataInTransaction(dbWrapper, conversationId,
1259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            false/* shouldAutoSwitchSelfId */, false/*archived*/);
1260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.setTransactionSuccessful();
1263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return count;
1264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.endTransaction();
1266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Deletes the conversation if there are zero non-draft messages left.
1271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * <p>
1272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * This is necessary because the telephony database has a trigger that deletes threads after
1273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * their last message is deleted. We need to ensure that if a thread goes away, we also delete
1274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * the conversation in Bugle. We don't store draft messages in telephony, so we ignore those
1275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * when querying for the # of messages in the conversation.
1276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
1277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return true if the conversation was deleted
1278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static boolean deleteConversationIfEmptyInTransaction(final DatabaseWrapper dbWrapper,
1281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId) {
1282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
1284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // TODO: The refreshConversationMetadataInTransaction method below uses this
1287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // same query; maybe they should share this logic?
1288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Check to see if there are any (non-draft) messages in the conversation
1290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
1291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    REFRESH_CONVERSATION_MESSAGE_PROJECTION,
1292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.CONVERSATION_ID + "=? AND " +
1293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
1294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { conversationId }, null, null,
1295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
1296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.getCount() == 0) {
1297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE,
1298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ConversationColumns._ID + "=?", new String[] { conversationId });
1299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                LogUtil.i(TAG,
1300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        "BugleDatabaseOperations: Deleted empty conversation " + conversationId);
1301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return true;
1302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
1303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return false;
1304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1307d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1308d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1309d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1310d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1311d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1312d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final String[] REFRESH_CONVERSATION_MESSAGE_PROJECTION = new String[] {
1313d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessageColumns._ID,
1314d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessageColumns.RECEIVED_TIMESTAMP,
1315d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessageColumns.SENDER_PARTICIPANT_ID
1316d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    };
1317d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1318d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1319d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Update conversation snippet, timestamp and optionally self id to match latest message in
1320d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * conversation.
1321d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1322d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1323d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void refreshConversationMetadataInTransaction(final DatabaseWrapper dbWrapper,
1324d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final boolean shouldAutoSwitchSelfId,
1325d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            boolean keepArchived) {
1326d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1327d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
1328d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1329d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1330d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Check to see if there are any (non-draft) messages in the conversation
1331d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
1332d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    REFRESH_CONVERSATION_MESSAGE_PROJECTION,
1333d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.CONVERSATION_ID + "=? AND " +
1334d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
1335d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { conversationId }, null, null,
1336d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
1337d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1338d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
1339d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Refresh latest message in conversation
1340d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final String latestMessageId = cursor.getString(0);
1341d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final long latestMessageTimestamp = cursor.getLong(1);
1342d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final String senderParticipantId = cursor.getString(2);
1343d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final boolean senderBlocked = isBlockedParticipant(dbWrapper, senderParticipantId);
1344d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                updateConversationMetadataInTransaction(dbWrapper, conversationId,
1345d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        latestMessageId, latestMessageTimestamp, senderBlocked || keepArchived,
1346d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        shouldAutoSwitchSelfId);
1347d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1348d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1349d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1350d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1351d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1352d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1353d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1354d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1355d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1356d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * When moving/removing an existing message update conversation metadata if necessary
1357d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper      db wrapper
1358d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param conversationId conversation to modify
1359d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param messageId      message that is leaving the conversation
1360d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a
1361d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *        result of this call when we see a new latest message?
1362d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param keepArchived   should we keep the conversation archived despite refresh
1363d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1364d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1365d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void maybeRefreshConversationMetadataInTransaction(
1366d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper dbWrapper, final String conversationId, final String messageId,
1367d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean shouldAutoSwitchSelfId, final boolean keepArchived) {
1368d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1369d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        boolean refresh = true;
1370d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!TextUtils.isEmpty(messageId)) {
1371d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            refresh = false;
1372d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Look for an existing conversation in the db with this conversation id
1373d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Cursor cursor = null;
1374d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            try {
1375d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
1376d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        new String[] { ConversationColumns.LATEST_MESSAGE_ID },
1377d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ConversationColumns._ID + "=?",
1378d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        new String[] { conversationId },
1379d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        null, null, null);
1380d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                Assert.inRange(cursor.getCount(), 0, 1);
1381d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (cursor.moveToFirst()) {
1382d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    refresh = TextUtils.equals(cursor.getString(0), messageId);
1383d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1384d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } finally {
1385d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (cursor != null) {
1386d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    cursor.close();
1387d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1388d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1389d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1390d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (refresh) {
1391d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // TODO: I think it is okay to delete the conversation if it is empty...
1392d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            refreshConversationMetadataInTransaction(dbWrapper, conversationId,
1393d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    shouldAutoSwitchSelfId, keepArchived);
1394d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1395d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1396d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1397d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1398d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1399d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // SQL statement to query latest message if for particular conversation
1400d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final String QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL = "SELECT "
1401d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            + ConversationColumns.LATEST_MESSAGE_ID + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
1402d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            + " WHERE " + ConversationColumns._ID + "=? LIMIT 1";
1403d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1404d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1405d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Note this is not thread safe so callers need to make sure they own the wrapper + statements
1406d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * while they call this and use the returned value.
1407d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1408d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1409d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static SQLiteStatement getQueryConversationsLatestMessageStatement(
1410d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper db, final String conversationId) {
1411d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1412d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final SQLiteStatement query = db.getStatementInTransaction(
1413d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                DatabaseWrapper.INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE,
1414d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL);
1415d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        query.clearBindings();
1416d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        query.bindString(1, conversationId);
1417d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return query;
1418d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1419d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1420d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // SQL statement to query latest message if for particular conversation
1421d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final String QUERY_MESSAGES_LATEST_MESSAGE_SQL = "SELECT "
1422d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            + MessageColumns._ID + " FROM " + DatabaseHelper.MESSAGES_TABLE
1423d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            + " WHERE " + MessageColumns.CONVERSATION_ID + "=? ORDER BY "
1424d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            + MessageColumns.RECEIVED_TIMESTAMP + " DESC LIMIT 1";
1425d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1426d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1427d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Note this is not thread safe so callers need to make sure they own the wrapper + statements
1428d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * while they call this and use the returned value.
1429d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1430d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1431d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static SQLiteStatement getQueryMessagesLatestMessageStatement(
1432d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper db, final String conversationId) {
1433d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1434d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final SQLiteStatement query = db.getStatementInTransaction(
1435d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                DatabaseWrapper.INDEX_QUERY_MESSAGES_LATEST_MESSAGE,
1436d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                QUERY_MESSAGES_LATEST_MESSAGE_SQL);
1437d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        query.clearBindings();
1438d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        query.bindString(1, conversationId);
1439d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return query;
1440d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1441d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1442d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1443d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Update conversation metadata if necessary
1444d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper      db wrapper
1445d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param conversationId conversation to modify
1446d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a
1447d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *                               result of this call when we see a new latest message?
1448d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param keepArchived if the conversation should be kept archived
1449d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1450d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1451d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void maybeRefreshConversationMetadataInTransaction(
1452d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper dbWrapper, final String conversationId,
1453d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean shouldAutoSwitchSelfId, boolean keepArchived) {
1454d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1455d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String currentLatestMessageId = null;
1456d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String latestMessageId = null;
1457d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1458d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final SQLiteStatement currentLatestMessageIdSql =
1459d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    getQueryConversationsLatestMessageStatement(dbWrapper, conversationId);
1460d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            currentLatestMessageId = currentLatestMessageIdSql.simpleQueryForString();
1461d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1462d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final SQLiteStatement latestMessageIdSql =
1463d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    getQueryMessagesLatestMessageStatement(dbWrapper, conversationId);
1464d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            latestMessageId = latestMessageIdSql.simpleQueryForString();
1465d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } catch (final SQLiteDoneException e) {
1466d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            LogUtil.e(TAG, "BugleDatabaseOperations: Query for latest message failed", e);
1467d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1468d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1469d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (TextUtils.isEmpty(currentLatestMessageId) ||
1470d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                !TextUtils.equals(currentLatestMessageId, latestMessageId)) {
1471d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            refreshConversationMetadataInTransaction(dbWrapper, conversationId,
1472d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    shouldAutoSwitchSelfId, keepArchived);
1473d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1474d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1475d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1476d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    static boolean getConversationExists(final DatabaseWrapper dbWrapper,
1477d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId) {
1478d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Look for an existing conversation in the db with this conversation id
1479d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1480d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1481d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
1482d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { /* No projection */},
1483d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationColumns._ID + "=?",
1484d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { conversationId },
1485d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    null, null, null);
1486d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return cursor.getCount() == 1;
1487d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1488d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1489d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1490d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1491d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1492d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1493d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1494d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /** Preserve parts in message but clear the stored draft */
1495d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static final int UPDATE_MODE_CLEAR_DRAFT = 1;
1496d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /** Add the message as a draft */
1497d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static final int UPDATE_MODE_ADD_DRAFT = 2;
1498d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1499d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1500d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Update draft message for specified conversation
1501d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper       local database (wrapped)
1502d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param conversationId  conversation to update
1503d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param message         Optional message to preserve attachments for (either as draft or for
1504d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *                        sending)
1505d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param updateMode      either {@link #UPDATE_MODE_CLEAR_DRAFT} or
1506d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *                        {@link #UPDATE_MODE_ADD_DRAFT}
1507d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return message id of newly written draft (else null)
1508d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1509d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1510d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String updateDraftMessageData(final DatabaseWrapper dbWrapper,
1511d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, @Nullable final MessageData message,
1512d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final int updateMode) {
1513d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1514d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.notNull(conversationId);
1515d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.inRange(updateMode, UPDATE_MODE_CLEAR_DRAFT, UPDATE_MODE_ADD_DRAFT);
1516d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String messageId = null;
1517d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1518d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dbWrapper.beginTransaction();
1519d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1520d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Find all draft parts for the current conversation
1521d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final SimpleArrayMap<Uri, MessagePartData> currentDraftParts = new SimpleArrayMap<>();
1522d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.DRAFT_PARTS_VIEW,
1523d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessagePartData.getProjection(),
1524d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.CONVERSATION_ID + " =?",
1525d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { conversationId }, null, null, null);
1526d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            while (cursor.moveToNext()) {
1527d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final MessagePartData part = MessagePartData.createFromCursor(cursor);
1528d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (part.isAttachment()) {
1529d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    currentDraftParts.put(part.getContentUri(), part);
1530d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1531d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1532d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Optionally, preserve attachments for "message"
1533d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean conversationExists = getConversationExists(dbWrapper, conversationId);
1534d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (message != null && conversationExists) {
1535d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                for (final MessagePartData part : message.getParts()) {
1536d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    if (part.isAttachment()) {
1537d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        currentDraftParts.remove(part.getContentUri());
1538d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    }
1539d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1540d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1541d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1542d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Delete orphan content
1543d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            for (int index = 0; index < currentDraftParts.size(); index++) {
1544d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final MessagePartData part = currentDraftParts.valueAt(index);
1545d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                part.destroySync();
1546d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1547d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1548d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Delete existing draft (cascade deletes parts)
1549d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
1550d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
1551d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] {
1552d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
1553d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        conversationId
1554d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    });
1555d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1556d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Write new draft
1557d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (updateMode == UPDATE_MODE_ADD_DRAFT && message != null
1558d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    && message.hasContent() && conversationExists) {
1559d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                Assert.equals(MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
1560d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        message.getStatus());
1561d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1562d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Now add draft to message table
1563d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                insertNewMessageInTransaction(dbWrapper, message);
1564d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                messageId = message.getMessageId();
1565d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1566d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1567d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (conversationExists) {
1568d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                updateConversationDraftSnippetAndPreviewInTransaction(
1569d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        dbWrapper, conversationId, message);
1570d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1571d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (message != null && message.getSelfId() != null) {
1572d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    updateConversationSelfIdInTransaction(dbWrapper, conversationId,
1573d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            message.getSelfId());
1574d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1575d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1576d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1577d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.setTransactionSuccessful();
1578d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1579d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.endTransaction();
1580d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1581d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1582d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1583d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1584d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
1585d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            LogUtil.v(TAG,
1586d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    "Updated draft message " + messageId + " for conversation " + conversationId);
1587d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1588d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return messageId;
1589d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1590d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1591d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1592d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Read the first draft message associated with this conversation.
1593d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * If none present create an empty (sms) draft message.
1594d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1595d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1596d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static MessageData readDraftMessageData(final DatabaseWrapper dbWrapper,
1597d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String conversationId, final String conversationSelfId) {
1598d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1599d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessageData message = null;
1600d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1601d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dbWrapper.beginTransaction();
1602d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1603d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
1604d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageData.getProjection(),
1605d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
1606d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] {
1607d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
1608d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        conversationId
1609d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    }, null, null, null);
1610d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
1611d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
1612d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                message = new MessageData();
1613d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                message.bindDraft(cursor, conversationSelfId);
1614d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                readMessagePartsData(dbWrapper, message, true);
1615d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Disconnect draft parts from DB
1616d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                for (final MessagePartData part : message.getParts()) {
1617d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    part.updatePartId(null);
1618d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    part.updateMessageId(null);
1619d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1620d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                message.updateMessageId(null);
1621d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1622d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.setTransactionSuccessful();
1623d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1624d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.endTransaction();
1625d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1626d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1627d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1628d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1629d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return message;
1630d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1631d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1632d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Internal
1633d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static void addParticipantToConversation(final DatabaseWrapper dbWrapper,
1634d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final ParticipantData participant, final String conversationId) {
1635d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String participantId = getOrCreateParticipantInTransaction(dbWrapper, participant);
1636d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.notNull(participantId);
1637d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1638d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Add the participant to the conversation participants table
1639d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
1640d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationParticipantsColumns.CONVERSATION_ID, conversationId);
1641d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ConversationParticipantsColumns.PARTICIPANT_ID, participantId);
1642d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dbWrapper.insert(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE, null, values);
1643d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1644d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1645d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1646d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get string used as canonical recipient for participant cache for sub id
1647d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1648d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static String getCanonicalRecipientFromSubId(final int subId) {
1649d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return "SELF(" + subId + ")";
1650d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1651d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1652d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1653d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Maps from a sub id or phone number to a participant id if there is one.
1654d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
1655d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return If the participant is available in our cache, or the DB, this returns the
1656d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * participant id for the given subid/phone number.  Otherwise it returns null.
1657d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1658d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @VisibleForTesting
1659d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static String getParticipantId(final DatabaseWrapper dbWrapper,
1660d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final int subId, final String canonicalRecipient) {
1661d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // First check our memory cache for the participant Id
1662d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String participantId;
1663d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
1664d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            participantId = sNormalizedPhoneNumberToParticipantIdCache.get(canonicalRecipient);
1665d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1666d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1667d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (participantId != null) {
1668d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return participantId;
1669d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1670d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1671d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // This code will only be executed for incremental additions.
1672d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1673d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1674d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (subId != ParticipantData.OTHER_THAN_SELF_SUB_ID) {
1675d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Now look for an existing participant in the db with this sub id.
1676d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
1677d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        new String[] {ParticipantColumns._ID},
1678d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ParticipantColumns.SUB_ID + "=?",
1679d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        new String[] { Integer.toString(subId) }, null, null, null);
1680d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
1681d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Look for existing participant with this normalized phone number and no subId.
1682d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
1683d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        new String[] {ParticipantColumns._ID},
1684d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ParticipantColumns.NORMALIZED_DESTINATION + "=? AND "
1685d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                + ParticipantColumns.SUB_ID + "=?",
1686d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                new String[] {canonicalRecipient, Integer.toString(subId)},
1687d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                null, null, null);
1688d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1689d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1690d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
1691d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // TODO Is this assert correct for multi-sim where a new sim was put in?
1692d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                Assert.isTrue(cursor.getCount() == 1);
1693d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1694d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // We found an existing participant in the database
1695d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                participantId = cursor.getString(0);
1696d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1697d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
1698d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    // Add it to the cache for next time
1699d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient,
1700d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            participantId);
1701d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1702d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1703d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1704d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1705d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1706d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1707d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1708d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return participantId;
1709d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1710d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1711d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1712d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static ParticipantData getOrCreateSelf(final DatabaseWrapper dbWrapper,
1713d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final int subId) {
1714d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1715d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        ParticipantData participant = null;
1716d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dbWrapper.beginTransaction();
1717d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1718d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final ParticipantData shell = ParticipantData.getSelfParticipant(subId);
1719d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String participantId = getOrCreateParticipantInTransaction(dbWrapper, shell);
1720d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            participant = getExistingParticipant(dbWrapper, participantId);
1721d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.setTransactionSuccessful();
1722d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1723d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dbWrapper.endTransaction();
1724d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1725d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return participant;
1726d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1727d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1728d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1729d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Lookup and if necessary create a new participant
1730d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param dbWrapper      Database wrapper
1731d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param participant    Participant to find/create
1732d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return participantId ParticipantId for existing or newly created participant
1733d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1734d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1735d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getOrCreateParticipantInTransaction(final DatabaseWrapper dbWrapper,
1736d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final ParticipantData participant) {
1737d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1738d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isTrue(dbWrapper.getDatabase().inTransaction());
1739d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int subId = ParticipantData.OTHER_THAN_SELF_SUB_ID;
1740d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String participantId = null;
1741d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        String canonicalRecipient = null;
1742d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (participant.isSelf()) {
1743d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            subId = participant.getSubId();
1744d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            canonicalRecipient = getCanonicalRecipientFromSubId(subId);
1745d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
1746d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            canonicalRecipient = participant.getNormalizedDestination();
1747d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1748d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.notNull(canonicalRecipient);
1749d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        participantId = getParticipantId(dbWrapper, subId, canonicalRecipient);
1750d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1751d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (participantId != null) {
1752d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return participantId;
1753d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1754d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1755d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!participant.isContactIdResolved()) {
1756d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Refresh participant's name and avatar with matching contact in CP2.
1757d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            ParticipantRefresh.refreshParticipant(dbWrapper, participant);
1758d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1759d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1760d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Insert the participant into the participants table
1761d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = participant.toContentValues();
1762d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final long participantRow = dbWrapper.insert(DatabaseHelper.PARTICIPANTS_TABLE, null,
1763d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                values);
1764d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        participantId = Long.toString(participantRow);
1765d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.notNull(canonicalRecipient);
1766d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1767d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
1768d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Now that we've inserted it, add it to our cache
1769d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient, participantId);
1770d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1771d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1772d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return participantId;
1773d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1774d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1775d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1776d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void updateDestination(final DatabaseWrapper dbWrapper,
1777d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String destination, final boolean blocked) {
1778d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1779d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ContentValues values = new ContentValues();
1780d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        values.put(ParticipantColumns.BLOCKED, blocked ? 1 : 0);
1781d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dbWrapper.update(DatabaseHelper.PARTICIPANTS_TABLE, values,
1782d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                ParticipantColumns.NORMALIZED_DESTINATION + "=? AND " +
1783d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ParticipantColumns.SUB_ID + "=?",
1784d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                new String[] { destination, Integer.toString(
1785d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        ParticipantData.OTHER_THAN_SELF_SUB_ID) });
1786d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1787d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1788d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1789d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static String getConversationFromOtherParticipantDestination(
1790d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final DatabaseWrapper db, final String otherDestination) {
1791d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1792d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Cursor cursor = null;
1793d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1794d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
1795d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { ConversationColumns._ID },
1796d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + "=?",
1797d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    new String[] { otherDestination }, null, null, null);
1798d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.inRange(cursor.getCount(), 0, 1);
1799d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor.moveToFirst()) {
1800d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return cursor.getString(0);
1801d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1802d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1803d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1804d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cursor.close();
1805d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1806d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1807d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return null;
1808d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1809d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1810d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1811d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1812d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Get a list of conversations that contain any of participants specified.
1813d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1814d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static HashSet<String> getConversationsForParticipants(
1815d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final ArrayList<String> participantIds) {
1816d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final DatabaseWrapper db = DataModel.get().getDatabase();
1817d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final HashSet<String> conversationIds = new HashSet<String>();
1818d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1819d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String selection = ConversationParticipantsColumns.PARTICIPANT_ID + "=?";
1820d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final String participantId : participantIds) {
1821d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String[] selectionArgs = new String[] { participantId };
1822d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final Cursor cursor = db.query(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE,
1823d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationParticipantsQuery.PROJECTION,
1824d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    selection, selectionArgs, null, null, null);
1825d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1826d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (cursor != null) {
1827d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                try {
1828d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    while (cursor.moveToNext()) {
1829d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        final String conversationId = cursor.getString(
1830d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                ConversationParticipantsQuery.INDEX_CONVERSATION_ID);
1831d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        conversationIds.add(conversationId);
1832d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    }
1833d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                } finally {
1834d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    cursor.close();
1835d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
1836d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1837d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1838d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1839d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return conversationIds;
1840d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1841d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1842d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1843d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Refresh conversation names/avatars based on a list of participants that are changed.
1844d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1845d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1846d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void refreshConversationsForParticipants(final ArrayList<String> participants) {
1847d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1848d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final HashSet<String> conversationIds = getConversationsForParticipants(participants);
1849d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (conversationIds.size() > 0) {
1850d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            for (final String conversationId : conversationIds) {
1851d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                refreshConversation(conversationId);
1852d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1853d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1854d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            MessagingContentProvider.notifyConversationListChanged();
1855d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
1856d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                LogUtil.v(TAG, "Number of conversations refreshed:" + conversationIds.size());
1857d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1858d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1859d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1860d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1861d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1862d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Refresh conversation names/avatars based on a changed participant.
1863d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1864d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1865d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void refreshConversationsForParticipant(final String participantId) {
1866d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1867d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<String> participantList = new ArrayList<String>(1);
1868d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        participantList.add(participantId);
1869d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        refreshConversationsForParticipants(participantList);
1870d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1871d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1872d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
1873d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Refresh one conversation.
1874d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
1875d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static void refreshConversation(final String conversationId) {
1876d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final DatabaseWrapper db = DataModel.get().getDatabase();
1877d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1878d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        db.beginTransaction();
1879d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
1880d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            BugleDatabaseOperations.updateConversationNameAndAvatarInTransaction(db,
1881d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    conversationId);
1882d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            db.setTransactionSuccessful();
1883d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } finally {
1884d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            db.endTransaction();
1885d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1886d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1887d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessagingContentProvider.notifyParticipantsChanged(conversationId);
1888d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessagingContentProvider.notifyMessagesChanged(conversationId);
1889d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
1890d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1891d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1892d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @DoesNotRunOnMainThread
1893d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static boolean updateRowIfExists(final DatabaseWrapper db, final String table,
1894d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final String rowKey, final String rowId, final ContentValues values) {
1895d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.isNotMainThread();
1896d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final StringBuilder sb = new StringBuilder();
1897d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<String> whereValues = new ArrayList<String>(values.size() + 1);
1898d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        whereValues.add(rowId);
1899d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1900d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (final String key : values.keySet()) {
1901d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (sb.length() > 0) {
1902d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                sb.append(" OR ");
1903d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1904d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final Object value = values.get(key);
1905d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sb.append(key);
1906d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (value != null) {
1907d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                sb.append(" IS NOT ?");
1908d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                whereValues.add(value.toString());
1909d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
1910d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                sb.append(" IS NOT NULL");
1911d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
1912d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1913d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
1914d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String whereClause = rowKey + "=?" + " AND (" + sb.toString() + ")";
1915d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String [] whereValuesArray = whereValues.toArray(new String[whereValues.size()]);
1916d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int count = db.update(table, values, whereClause, whereValuesArray);
1917d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (count > 1) {
1918d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            LogUtil.w(LogUtil.BUGLE_TAG, "Updated more than 1 row " + count + "; " + table +
1919d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    " for " + rowKey + " = " + rowId + " (deleted?)");
1920d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
1921d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.inRange(count, 0, 1);
1922d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return (count >= 0);
1923d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
1924d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd}
1925