blob: 9c2bb38fd9191e0487ff17c6bb9d1911f11caef3 [file] [log] [blame]
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.providers.telephony;
18
kaiyize5a13fa2014-09-15 15:36:54 +080019import java.util.Locale;
20
Dianne Hackbornf27792f2013-02-04 18:26:53 -080021import android.app.AppOpsManager;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080022import android.content.ContentProvider;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.UriMatcher;
26import android.database.Cursor;
27import android.database.DatabaseUtils;
28import android.database.sqlite.SQLiteDatabase;
29import android.database.sqlite.SQLiteOpenHelper;
30import android.database.sqlite.SQLiteQueryBuilder;
31import android.net.Uri;
Ye Wene07acb92014-11-19 12:06:05 -080032import android.os.Binder;
Amith Yamasani43f9fb22014-09-10 15:56:47 -070033import android.os.UserHandle;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080034import android.provider.BaseColumns;
Ye Wen86b8a2c2015-06-11 11:25:31 -070035import android.provider.Telephony;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080036import android.provider.Telephony.CanonicalAddressesColumns;
37import android.provider.Telephony.Mms;
38import android.provider.Telephony.MmsSms;
Ye Wene07acb92014-11-19 12:06:05 -080039import android.provider.Telephony.MmsSms.PendingMessages;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080040import android.provider.Telephony.Sms;
Ye Wene07acb92014-11-19 12:06:05 -080041import android.provider.Telephony.Sms.Conversations;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080042import android.provider.Telephony.Threads;
43import android.provider.Telephony.ThreadsColumns;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080044import android.text.TextUtils;
45import android.util.Log;
46
Tom Taylorb1bae652010-03-08 16:33:42 -080047import com.google.android.mms.pdu.PduHeaders;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080048
Ye Wen86b8a2c2015-06-11 11:25:31 -070049import java.io.FileDescriptor;
50import java.io.PrintWriter;
Ye Wene07acb92014-11-19 12:06:05 -080051import java.util.Arrays;
52import java.util.HashSet;
53import java.util.List;
54import java.util.Set;
55
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080056/**
57 * This class provides the ability to query the MMS and SMS databases
58 * at the same time, mixing messages from both in a single thread
59 * (A.K.A. conversation).
60 *
61 * A virtual column, MmsSms.TYPE_DISCRIMINATOR_COLUMN, may be
62 * requested in the projection for a query. Its value is either "mms"
63 * or "sms", depending on whether the message represented by the row
64 * is an MMS message or an SMS message, respectively.
65 *
66 * This class also provides the ability to find out what addresses
67 * participated in a particular thread. It doesn't support updates
68 * for either of these.
69 *
70 * This class provides a way to allocate and retrieve thread IDs.
71 * This is done atomically through a query. There is no insert URI
72 * for this.
73 *
74 * Finally, this class provides a way to delete or update all messages
75 * in a thread.
76 */
77public class MmsSmsProvider extends ContentProvider {
78 private static final UriMatcher URI_MATCHER =
79 new UriMatcher(UriMatcher.NO_MATCH);
80 private static final String LOG_TAG = "MmsSmsProvider";
Tom Taylor816e9342009-09-03 17:09:03 -070081 private static final boolean DEBUG = false;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080082
83 private static final String NO_DELETES_INSERTS_OR_UPDATES =
84 "MmsSmsProvider does not support deletes, inserts, or updates for this URI.";
Tom Taylorf0a7f152009-08-31 17:56:47 -070085 private static final int URI_CONVERSATIONS = 0;
86 private static final int URI_CONVERSATIONS_MESSAGES = 1;
87 private static final int URI_CONVERSATIONS_RECIPIENTS = 2;
88 private static final int URI_MESSAGES_BY_PHONE = 3;
89 private static final int URI_THREAD_ID = 4;
90 private static final int URI_CANONICAL_ADDRESS = 5;
91 private static final int URI_PENDING_MSG = 6;
92 private static final int URI_COMPLETE_CONVERSATIONS = 7;
93 private static final int URI_UNDELIVERED_MSG = 8;
94 private static final int URI_CONVERSATIONS_SUBJECT = 9;
95 private static final int URI_NOTIFICATIONS = 10;
96 private static final int URI_OBSOLETE_THREADS = 11;
97 private static final int URI_DRAFT = 12;
98 private static final int URI_CANONICAL_ADDRESSES = 13;
99 private static final int URI_SEARCH = 14;
Mark Wagner8e5ee782010-01-04 17:39:06 -0800100 private static final int URI_SEARCH_SUGGEST = 15;
101 private static final int URI_FIRST_LOCKED_MESSAGE_ALL = 16;
102 private static final int URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID = 17;
Mark Wagner6a917902010-05-03 12:52:23 -0700103 private static final int URI_MESSAGE_ID_TO_THREAD = 18;
Mengjun Leng6709d702014-09-23 12:11:00 +0800104 private static final int URI_MAILBOX_MESSAGES = 19;
yanglv23c51f82014-08-08 14:50:21 +0800105 private static final int URI_SEARCH_MESSAGE = 20;
106 private static final int URI_MESSAGES_COUNT = 21;
kaiyiza4238572014-08-09 15:07:18 +0800107 private static final int URI_UPDATE_THREAD_DATE = 22;
kaiyizd7f15a12014-11-28 17:52:35 +0800108 private static final int URI_UPDATE_THREAD = 23;
109
yanglv23c51f82014-08-08 14:50:21 +0800110 // Escape character
111 private static final char SEARCH_ESCAPE_CHARACTER = '!';
112
113 public static final int SEARCH_MODE_CONTENT = 0;
114 public static final int SEARCH_MODE_NAME = 1;
115 public static final int SEARCH_MODE_NUMBER = 2;
116 public static final int SEARCH_MODE_SUBJECT = 3;
117
118 // add for different match mode in classify search
119 public static final int MATCH_BY_ADDRESS = 0;
120 public static final int MATCH_BY_THREAD_ID = 1;
121
122 private static final long RESULT_FOR_ID_NOT_FOUND= -1L;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800123
124 /**
125 * the name of the table that is used to store the queue of
126 * messages(both MMS and SMS) to be sent/downloaded.
127 */
128 public static final String TABLE_PENDING_MSG = "pending_msgs";
129
Wei Huang1ecf1922009-11-08 12:12:07 -0800130 /**
131 * the name of the table that is used to store the canonical addresses for both SMS and MMS.
132 */
133 private static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses";
134
yanglv23c51f82014-08-08 14:50:21 +0800135 private static final String DEFAULT_STRING_ZERO = "0";
136
Tom Taylor15156cd2012-11-15 14:15:46 -0800137 /**
138 * the name of the table that is used to store the conversation threads.
139 */
140 static final String TABLE_THREADS = "threads";
141
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800142 // These constants are used to construct union queries across the
143 // MMS and SMS base tables.
144
145 // These are the columns that appear in both the MMS ("pdu") and
146 // SMS ("sms") message tables.
147 private static final String[] MMS_SMS_COLUMNS =
Ye Wencfb8bbd2014-06-17 09:00:38 -0700148 { BaseColumns._ID, Mms.DATE, Mms.DATE_SENT, Mms.READ, Mms.THREAD_ID, Mms.LOCKED,
Suresh Koleti02354f12014-08-19 16:21:45 +0530149 Mms.SUBSCRIPTION_ID, Mms.PHONE_ID };
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800150
151 // These are the columns that appear only in the MMS message
152 // table.
153 private static final String[] MMS_ONLY_COLUMNS = {
154 Mms.CONTENT_CLASS, Mms.CONTENT_LOCATION, Mms.CONTENT_TYPE,
155 Mms.DELIVERY_REPORT, Mms.EXPIRY, Mms.MESSAGE_CLASS, Mms.MESSAGE_ID,
156 Mms.MESSAGE_SIZE, Mms.MESSAGE_TYPE, Mms.MESSAGE_BOX, Mms.PRIORITY,
157 Mms.READ_STATUS, Mms.RESPONSE_STATUS, Mms.RESPONSE_TEXT,
158 Mms.RETRIEVE_STATUS, Mms.RETRIEVE_TEXT_CHARSET, Mms.REPORT_ALLOWED,
159 Mms.READ_REPORT, Mms.STATUS, Mms.SUBJECT, Mms.SUBJECT_CHARSET,
Tom Taylorf88d1d62012-09-07 13:38:39 -0700160 Mms.TRANSACTION_ID, Mms.MMS_VERSION, Mms.TEXT_ONLY };
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800161
162 // These are the columns that appear only in the SMS message
163 // table.
164 private static final String[] SMS_ONLY_COLUMNS =
165 { "address", "body", "person", "reply_path_present",
Tom Taylorddf267c2009-10-28 18:14:55 -0700166 "service_center", "status", "subject", "type", "error_code" };
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800167
168 // These are all the columns that appear in the "threads" table.
169 private static final String[] THREADS_COLUMNS = {
170 BaseColumns._ID,
171 ThreadsColumns.DATE,
172 ThreadsColumns.RECIPIENT_IDS,
173 ThreadsColumns.MESSAGE_COUNT
174 };
175
Wei Huang1ecf1922009-11-08 12:12:07 -0800176 private static final String[] CANONICAL_ADDRESSES_COLUMNS_1 =
177 new String[] { CanonicalAddressesColumns.ADDRESS };
178
179 private static final String[] CANONICAL_ADDRESSES_COLUMNS_2 =
180 new String[] { CanonicalAddressesColumns._ID,
181 CanonicalAddressesColumns.ADDRESS };
182
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800183 // These are all the columns that appear in the MMS and SMS
184 // message tables.
185 private static final String[] UNION_COLUMNS =
186 new String[MMS_SMS_COLUMNS.length
187 + MMS_ONLY_COLUMNS.length
188 + SMS_ONLY_COLUMNS.length];
189
190 // These are all the columns that appear in the MMS table.
191 private static final Set<String> MMS_COLUMNS = new HashSet<String>();
192
193 // These are all the columns that appear in the SMS table.
194 private static final Set<String> SMS_COLUMNS = new HashSet<String>();
195
196 private static final String VND_ANDROID_DIR_MMS_SMS =
197 "vnd.android-dir/mms-sms";
198
199 private static final String[] ID_PROJECTION = { BaseColumns._ID };
200
201 private static final String[] EMPTY_STRING_ARRAY = new String[0];
202
Tom Taylor3e1cba82012-05-11 14:08:45 -0700203 private static final String[] SEARCH_STRING = new String[1];
yanglv23c51f82014-08-08 14:50:21 +0800204 private static final String SEARCH_QUERY = "SELECT index_text as snippet FROM " +
205 "words WHERE index_text like ? ORDER BY snippet LIMIT 50;";
Tom Taylor3e1cba82012-05-11 14:08:45 -0700206
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800207 private static final String SMS_CONVERSATION_CONSTRAINT = "(" +
208 Sms.TYPE + " != " + Sms.MESSAGE_TYPE_DRAFT + ")";
209
210 private static final String MMS_CONVERSATION_CONSTRAINT = "(" +
211 Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS + " AND (" +
212 Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_SEND_REQ + " OR " +
213 Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + " OR " +
214 Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + "))";
215
Ye Wen72f13552015-03-10 14:17:13 -0700216 private static String getTextSearchQuery(String smsTable, String pduTable) {
217 // Search on the words table but return the rows from the corresponding sms table
218 final String smsQuery = "SELECT "
219 + smsTable + "._id AS _id,"
220 + "thread_id,"
221 + "address,"
222 + "body,"
223 + "date,"
224 + "date_sent,"
225 + "index_text,"
226 + "words._id "
227 + "FROM " + smsTable + ",words "
yanglv23c51f82014-08-08 14:50:21 +0800228 + "WHERE (index_text LIKE ? "
Ye Wen72f13552015-03-10 14:17:13 -0700229 + "AND " + smsTable + "._id=words.source_id "
230 + "AND words.table_to_use=1)";
Chen Mike01c75ba2010-11-24 14:21:31 +0100231
Ye Wen72f13552015-03-10 14:17:13 -0700232 // Search on the words table but return the rows from the corresponding parts table
233 final String mmsQuery = "SELECT "
234 + pduTable + "._id,"
235 + "thread_id,"
236 + "addr.address,"
237 + "part.text AS body,"
238 + pduTable + ".date,"
239 + pduTable + ".date_sent,"
240 + "index_text,"
241 + "words._id "
242 + "FROM " + pduTable + ",part,addr,words "
243 + "WHERE ((part.mid=" + pduTable + "._id) "
244 + "AND (addr.msg_id=" + pduTable + "._id) "
245 + "AND (addr.type=" + PduHeaders.TO + ") "
246 + "AND (part.ct='text/plain') "
yanglv23c51f82014-08-08 14:50:21 +0800247 + "AND (index_text LIKE ?) "
Ye Wen72f13552015-03-10 14:17:13 -0700248 + "AND (part._id = words.source_id) "
249 + "AND (words.table_to_use=2))";
Chen Mike01c75ba2010-11-24 14:21:31 +0100250
Ye Wen72f13552015-03-10 14:17:13 -0700251 // This code queries the sms and mms tables and returns a unified result set
252 // of text matches. We query the sms table which is pretty simple. We also
253 // query the pdu, part and addr table to get the mms result. Note we're
254 // using a UNION so we have to have the same number of result columns from
255 // both queries.
256 return smsQuery + " UNION " + mmsQuery + " "
257 + "GROUP BY thread_id "
258 + "ORDER BY thread_id ASC, date DESC";
259 }
Chen Mike01c75ba2010-11-24 14:21:31 +0100260
yanglv23c51f82014-08-08 14:50:21 +0800261 private static final String SMS_PROJECTION = "'sms' AS transport_type, _id, thread_id,"
262 + "address, body, sub_id, date, date_sent, read, type,"
263 + "status, locked, NULL AS error_code,"
264 + "NULL AS sub, NULL AS sub_cs, date, date_sent, read,"
265 + "NULL as m_type,"
266 + "NULL AS msg_box,"
267 + "NULL AS d_rpt, NULL AS rr, NULL AS err_type,"
268 + "locked, NULL AS st, NULL AS text_only,"
269 + "sub_id, NULL AS recipient_ids";
270 private static final String MMS_PROJECTION = "'mms' AS transport_type, pdu._id, thread_id,"
271 + "addr.address AS address, part.text as body, sub_id,"
272 + "pdu.date * 1000 AS date, date_sent, read, NULL AS type,"
273 + "NULL AS status, locked, NULL AS error_code,"
274 + "sub, sub_cs, date, date_sent, read,"
275 + "m_type,"
276 + "pdu.msg_box AS msg_box,"
277 + "d_rpt, rr, NULL AS err_type,"
278 + "locked, NULL AS st, NULL AS text_only,"
279 + "sub_id, NULL AS recipient_ids";
280 private static final String MMS_PROJECTION_FOR_NUMBER_SEARCH =
281 "'mms' AS transport_type, pdu._id, thread_id,"
282 + "addr.address AS address, NULL AS body, sub_id,"
283 + "pdu.date * 1000 AS date, date_sent, read, NULL AS type,"
284 + "NULL AS status, locked, NULL AS error_code,"
285 + "sub, sub_cs, date, date_sent, read,"
286 + "m_type,"
287 + "pdu.msg_box AS msg_box,"
288 + "d_rpt, rr, NULL AS err_type,"
289 + "locked, NULL AS st, NULL AS text_only,"
290 + "sub_id, NULL AS recipient_ids";
291 private static final String MMS_PROJECTION_FOR_SUBJECT_SEARCH =
292 "'mms' AS transport_type, pdu._id, thread_id,"
293 + "addr.address AS address, pdu.sub as body, phone_id,"
294 + "pdu.date * 1000 AS date, date_sent, read, NULL AS type,"
295 + "NULL AS status, locked, NULL AS error_code,"
296 + "sub, sub_cs, date, date_sent, read,"
297 + "m_type,"
298 + "pdu.msg_box AS msg_box,"
299 + "d_rpt, rr, NULL AS err_type,"
300 + "locked, NULL AS st, NULL AS text_only,"
301 + "phone_id, NULL AS recipient_ids";
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800302 private static final String AUTHORITY = "mms-sms";
303
304 static {
305 URI_MATCHER.addURI(AUTHORITY, "conversations", URI_CONVERSATIONS);
306 URI_MATCHER.addURI(AUTHORITY, "complete-conversations", URI_COMPLETE_CONVERSATIONS);
307
308 // In these patterns, "#" is the thread ID.
309 URI_MATCHER.addURI(
310 AUTHORITY, "conversations/#", URI_CONVERSATIONS_MESSAGES);
311 URI_MATCHER.addURI(
312 AUTHORITY, "conversations/#/recipients",
313 URI_CONVERSATIONS_RECIPIENTS);
314
315 URI_MATCHER.addURI(
316 AUTHORITY, "conversations/#/subject",
317 URI_CONVERSATIONS_SUBJECT);
318
Mengjun Leng6709d702014-09-23 12:11:00 +0800319 //"#" is the mailbox name id, such as inbox=1, sent=2, draft = 3 , outbox = 4
320 URI_MATCHER.addURI(AUTHORITY, "mailbox/#", URI_MAILBOX_MESSAGES);
321
yanglv89620c02014-08-05 10:58:58 +0800322 // URI for obtaining all short message count
323 URI_MATCHER.addURI(AUTHORITY, "messagescount", URI_MESSAGES_COUNT);
324
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800325 // URI for deleting obsolete threads.
326 URI_MATCHER.addURI(AUTHORITY, "conversations/obsolete", URI_OBSOLETE_THREADS);
327
yanglv23c51f82014-08-08 14:50:21 +0800328 // URI for search messages in mailbox mode with obtained search mode
329 // such as content, number and name
330 URI_MATCHER.addURI(AUTHORITY, "search-message", URI_SEARCH_MESSAGE);
331
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800332 URI_MATCHER.addURI(
333 AUTHORITY, "messages/byphone/*",
334 URI_MESSAGES_BY_PHONE);
335
336 // In this pattern, two query parameter names are expected:
337 // "subject" and "recipient." Multiple "recipient" parameters
338 // may be present.
339 URI_MATCHER.addURI(AUTHORITY, "threadID", URI_THREAD_ID);
340
kaiyizd7f15a12014-11-28 17:52:35 +0800341 URI_MATCHER.addURI(AUTHORITY, "update-thread/#", URI_UPDATE_THREAD);
342
kaiyiza4238572014-08-09 15:07:18 +0800343 URI_MATCHER.addURI(AUTHORITY, "update-date", URI_UPDATE_THREAD_DATE);
344
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800345 // Use this pattern to query the canonical address by given ID.
346 URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS);
347
Ficus Kirkpatrick37e44242009-05-12 14:26:14 -0700348 // Use this pattern to query all canonical addresses.
349 URI_MATCHER.addURI(AUTHORITY, "canonical-addresses", URI_CANONICAL_ADDRESSES);
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700350
Mark Wagner66d373c2009-06-08 09:52:00 -0700351 URI_MATCHER.addURI(AUTHORITY, "search", URI_SEARCH);
Mark Wagner8e5ee782010-01-04 17:39:06 -0800352 URI_MATCHER.addURI(AUTHORITY, "searchSuggest", URI_SEARCH_SUGGEST);
Ficus Kirkpatrick37e44242009-05-12 14:26:14 -0700353
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800354 // In this pattern, two query parameters may be supplied:
355 // "protocol" and "message." For example:
356 // content://mms-sms/pending?
357 // -> Return all pending messages;
358 // content://mms-sms/pending?protocol=sms
359 // -> Only return pending SMs;
360 // content://mms-sms/pending?protocol=mms&message=1
361 // -> Return the the pending MM which ID equals '1'.
362 //
363 URI_MATCHER.addURI(AUTHORITY, "pending", URI_PENDING_MSG);
364
365 // Use this pattern to get a list of undelivered messages.
366 URI_MATCHER.addURI(AUTHORITY, "undelivered", URI_UNDELIVERED_MSG);
367
368 // Use this pattern to see what delivery status reports (for
369 // both MMS and SMS) have not been delivered to the user.
370 URI_MATCHER.addURI(AUTHORITY, "notifications", URI_NOTIFICATIONS);
371
372 URI_MATCHER.addURI(AUTHORITY, "draft", URI_DRAFT);
Tom Taylorf0a7f152009-08-31 17:56:47 -0700373
374 URI_MATCHER.addURI(AUTHORITY, "locked", URI_FIRST_LOCKED_MESSAGE_ALL);
375
376 URI_MATCHER.addURI(AUTHORITY, "locked/#", URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID);
377
Mark Wagner6a917902010-05-03 12:52:23 -0700378 URI_MATCHER.addURI(AUTHORITY, "messageIdToThread", URI_MESSAGE_ID_TO_THREAD);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800379 initializeColumnSets();
380 }
381
382 private SQLiteOpenHelper mOpenHelper;
383
Daisuke Miyakawa845a9162009-09-19 19:42:14 -0700384 private boolean mUseStrictPhoneNumberComparation;
385
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800386 @Override
387 public boolean onCreate() {
Ye Wenb2ce2d32014-07-28 14:49:30 -0700388 setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
389 mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
Daisuke Miyakawa845a9162009-09-19 19:42:14 -0700390 mUseStrictPhoneNumberComparation =
391 getContext().getResources().getBoolean(
392 com.android.internal.R.bool.config_use_strict_phone_number_comparation);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800393 return true;
394 }
395
396 @Override
397 public Cursor query(Uri uri, String[] projection,
398 String selection, String[] selectionArgs, String sortOrder) {
Ye Wen72f13552015-03-10 14:17:13 -0700399 // First check if restricted views of the "sms" and "pdu" tables should be used based on the
400 // caller's identity. Only system, phone or the default sms app can have full access
401 // of sms/mms data. For other apps, we present a restricted view which only contains sent
402 // or received messages, without wap pushes.
403 final boolean accessRestricted = ProviderUtil.isAccessRestricted(
404 getContext(), getCallingPackage(), Binder.getCallingUid());
405 final String pduTable = MmsProvider.getPduTable(accessRestricted);
406 final String smsTable = SmsProvider.getSmsTable(accessRestricted);
407
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800408 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
409 Cursor cursor = null;
Ye Wen72f13552015-03-10 14:17:13 -0700410 final int match = URI_MATCHER.match(uri);
411 switch (match) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800412 case URI_COMPLETE_CONVERSATIONS:
Ye Wen72f13552015-03-10 14:17:13 -0700413 cursor = getCompleteConversations(projection, selection, sortOrder, smsTable,
414 pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800415 break;
416 case URI_CONVERSATIONS:
417 String simple = uri.getQueryParameter("simple");
418 if ((simple != null) && simple.equals("true")) {
419 String threadType = uri.getQueryParameter("thread_type");
420 if (!TextUtils.isEmpty(threadType)) {
421 selection = concatSelections(
422 selection, Threads.TYPE + "=" + threadType);
423 }
424 cursor = getSimpleConversations(
425 projection, selection, selectionArgs, sortOrder);
426 } else {
427 cursor = getConversations(
Ye Wen72f13552015-03-10 14:17:13 -0700428 projection, selection, sortOrder, smsTable, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800429 }
430 break;
431 case URI_CONVERSATIONS_MESSAGES:
Tom Taylorf008a612011-05-13 13:49:34 -0700432 cursor = getConversationMessages(uri.getPathSegments().get(1), projection,
Ye Wen72f13552015-03-10 14:17:13 -0700433 selection, sortOrder, smsTable, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800434 break;
Mengjun Leng6709d702014-09-23 12:11:00 +0800435 case URI_MAILBOX_MESSAGES:
436 cursor = getMailboxMessages(
437 uri.getPathSegments().get(1), projection, selection,
438 selectionArgs, sortOrder, false, pduTable);
439 break;
yanglv89620c02014-08-05 10:58:58 +0800440 case URI_MESSAGES_COUNT:
441 return getAllMessagesCount();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800442 case URI_CONVERSATIONS_RECIPIENTS:
443 cursor = getConversationById(
444 uri.getPathSegments().get(1), projection, selection,
445 selectionArgs, sortOrder);
446 break;
447 case URI_CONVERSATIONS_SUBJECT:
448 cursor = getConversationById(
449 uri.getPathSegments().get(1), projection, selection,
450 selectionArgs, sortOrder);
451 break;
452 case URI_MESSAGES_BY_PHONE:
453 cursor = getMessagesByPhoneNumber(
Ye Wen72f13552015-03-10 14:17:13 -0700454 uri.getPathSegments().get(2), projection, selection, sortOrder, smsTable,
455 pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800456 break;
457 case URI_THREAD_ID:
458 List<String> recipients = uri.getQueryParameters("recipient");
459
460 cursor = getThreadId(recipients);
461 break;
462 case URI_CANONICAL_ADDRESS: {
463 String extraSelection = "_id=" + uri.getPathSegments().get(1);
464 String finalSelection = TextUtils.isEmpty(selection)
465 ? extraSelection : extraSelection + " AND " + selection;
Wei Huang1ecf1922009-11-08 12:12:07 -0800466 cursor = db.query(TABLE_CANONICAL_ADDRESSES,
467 CANONICAL_ADDRESSES_COLUMNS_1,
468 finalSelection,
469 selectionArgs,
470 null, null,
471 sortOrder);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800472 break;
473 }
Ficus Kirkpatrick37e44242009-05-12 14:26:14 -0700474 case URI_CANONICAL_ADDRESSES:
Wei Huang1ecf1922009-11-08 12:12:07 -0800475 cursor = db.query(TABLE_CANONICAL_ADDRESSES,
476 CANONICAL_ADDRESSES_COLUMNS_2,
477 selection,
478 selectionArgs,
479 null, null,
480 sortOrder);
Ficus Kirkpatrick37e44242009-05-12 14:26:14 -0700481 break;
Mark Wagner8e5ee782010-01-04 17:39:06 -0800482 case URI_SEARCH_SUGGEST: {
yanglv23c51f82014-08-08 14:50:21 +0800483 String pattern = uri.getQueryParameter("pattern");
484 if (TextUtils.isEmpty(pattern)) {
485 return null;
486 } else {
487 SEARCH_STRING[0] = "%" + pattern + "%";
488 }
Mark Wagner9e1fd442011-09-30 16:25:45 -0700489 // find the words which match the pattern using the snippet function. The
490 // snippet function parameters mainly describe how to format the result.
491 // See http://www.sqlite.org/fts3.html#section_4_2 for details.
Mark Wagner8e5ee782010-01-04 17:39:06 -0800492 if ( sortOrder != null
493 || selection != null
494 || selectionArgs != null
495 || projection != null) {
496 throw new IllegalArgumentException(
497 "do not specify sortOrder, selection, selectionArgs, or projection" +
498 "with this query");
499 }
500
Tom Taylor3e1cba82012-05-11 14:08:45 -0700501 cursor = db.rawQuery(SEARCH_QUERY, SEARCH_STRING);
Mark Wagner8e5ee782010-01-04 17:39:06 -0800502 break;
503 }
Mark Wagner6a917902010-05-03 12:52:23 -0700504 case URI_MESSAGE_ID_TO_THREAD: {
505 // Given a message ID and an indicator for SMS vs. MMS return
506 // the thread id of the corresponding thread.
507 try {
508 long id = Long.parseLong(uri.getQueryParameter("row_id"));
509 switch (Integer.parseInt(uri.getQueryParameter("table_to_use"))) {
510 case 1: // sms
511 cursor = db.query(
Ye Wen72f13552015-03-10 14:17:13 -0700512 smsTable,
Mark Wagner6a917902010-05-03 12:52:23 -0700513 new String[] { "thread_id" },
514 "_id=?",
515 new String[] { String.valueOf(id) },
516 null,
517 null,
518 null);
519 break;
520 case 2: // mms
Ye Wen72f13552015-03-10 14:17:13 -0700521 String mmsQuery = "SELECT thread_id "
522 + "FROM " + pduTable + ",part "
523 + "WHERE ((part.mid=" + pduTable + "._id) "
524 + "AND " + "(part._id=?))";
Mark Wagner6a917902010-05-03 12:52:23 -0700525 cursor = db.rawQuery(mmsQuery, new String[] { String.valueOf(id) });
526 break;
527 }
528 } catch (NumberFormatException ex) {
529 // ignore... return empty cursor
530 }
531 break;
532 }
Mark Wagner8e5ee782010-01-04 17:39:06 -0800533 case URI_SEARCH: {
Tom Taylor87bfe142009-08-18 15:33:51 -0700534 if ( sortOrder != null
535 || selection != null
536 || selectionArgs != null
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700537 || projection != null) {
538 throw new IllegalArgumentException(
539 "do not specify sortOrder, selection, selectionArgs, or projection" +
540 "with this query");
Mark Wagner66d373c2009-06-08 09:52:00 -0700541 }
yanglv23c51f82014-08-08 14:50:21 +0800542 String pattern = uri.getQueryParameter("pattern");
543 if (TextUtils.isEmpty(pattern)) {
544 return null;
545 }
546 String searchString = "%" + pattern + "%";
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700547
Mark Wagner8e5ee782010-01-04 17:39:06 -0800548 try {
Ye Wen72f13552015-03-10 14:17:13 -0700549 cursor = db.rawQuery(getTextSearchQuery(smsTable, pduTable),
550 new String[] { searchString, searchString });
Mark Wagner8e5ee782010-01-04 17:39:06 -0800551 } catch (Exception ex) {
552 Log.e(LOG_TAG, "got exception: " + ex.toString());
553 }
Mark Wagner66d373c2009-06-08 09:52:00 -0700554 break;
Mark Wagner8e5ee782010-01-04 17:39:06 -0800555 }
yanglv23c51f82014-08-08 14:50:21 +0800556 case URI_SEARCH_MESSAGE:
557 cursor = getSearchMessages(uri, db);
558 break;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800559 case URI_PENDING_MSG: {
560 String protoName = uri.getQueryParameter("protocol");
561 String msgId = uri.getQueryParameter("message");
562 int proto = TextUtils.isEmpty(protoName) ? -1
563 : (protoName.equals("sms") ? MmsSms.SMS_PROTO : MmsSms.MMS_PROTO);
564
565 String extraSelection = (proto != -1) ?
566 (PendingMessages.PROTO_TYPE + "=" + proto) : " 0=0 ";
567 if (!TextUtils.isEmpty(msgId)) {
568 extraSelection += " AND " + PendingMessages.MSG_ID + "=" + msgId;
569 }
570
571 String finalSelection = TextUtils.isEmpty(selection)
572 ? extraSelection : ("(" + extraSelection + ") AND " + selection);
573 String finalOrder = TextUtils.isEmpty(sortOrder)
574 ? PendingMessages.DUE_TIME : sortOrder;
575 cursor = db.query(TABLE_PENDING_MSG, null,
576 finalSelection, selectionArgs, null, null, finalOrder);
577 break;
578 }
579 case URI_UNDELIVERED_MSG: {
580 cursor = getUndeliveredMessages(projection, selection,
Ye Wen72f13552015-03-10 14:17:13 -0700581 selectionArgs, sortOrder, smsTable, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800582 break;
583 }
584 case URI_DRAFT: {
Ye Wen72f13552015-03-10 14:17:13 -0700585 cursor = getDraftThread(projection, selection, sortOrder, smsTable, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800586 break;
587 }
Tom Taylorf0a7f152009-08-31 17:56:47 -0700588 case URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID: {
589 long threadId;
590 try {
591 threadId = Long.parseLong(uri.getLastPathSegment());
592 } catch (NumberFormatException e) {
593 Log.e(LOG_TAG, "Thread ID must be a long.");
594 break;
595 }
596 cursor = getFirstLockedMessage(projection, "thread_id=" + Long.toString(threadId),
Ye Wen72f13552015-03-10 14:17:13 -0700597 sortOrder, smsTable, pduTable);
Tom Taylorf0a7f152009-08-31 17:56:47 -0700598 break;
599 }
600 case URI_FIRST_LOCKED_MESSAGE_ALL: {
Ye Wen72f13552015-03-10 14:17:13 -0700601 cursor = getFirstLockedMessage(
602 projection, selection, sortOrder, smsTable, pduTable);
Tom Taylorf0a7f152009-08-31 17:56:47 -0700603 break;
604 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800605 default:
606 throw new IllegalStateException("Unrecognized URI:" + uri);
607 }
608
Tom Taylor59269962012-05-09 14:42:35 -0700609 if (cursor != null) {
610 cursor.setNotificationUri(getContext().getContentResolver(), MmsSms.CONTENT_URI);
611 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800612 return cursor;
613 }
614
615 /**
616 * Return the canonical address ID for this address.
617 */
618 private long getSingleAddressId(String address) {
619 boolean isEmail = Mms.isEmailAddress(address);
Tom Taylore8a24dd2011-01-07 12:15:34 -0800620 boolean isPhoneNumber = Mms.isPhoneNumber(address);
621
622 // We lowercase all email addresses, but not addresses that aren't numbers, because
623 // that would incorrectly turn an address such as "My Vodafone" into "my vodafone"
624 // and the thread title would be incorrect when displayed in the UI.
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800625 String refinedAddress = isEmail ? address.toLowerCase() : address;
Tom Taylore8a24dd2011-01-07 12:15:34 -0800626
Wei Huangc818d632009-09-28 14:18:47 -0700627 String selection = "address=?";
628 String[] selectionArgs;
Wei Huang14595cb2010-04-08 11:33:26 -0700629 long retVal = -1L;
Wei Huangc818d632009-09-28 14:18:47 -0700630
Tom Taylore8a24dd2011-01-07 12:15:34 -0800631 if (!isPhoneNumber) {
Wei Huangc818d632009-09-28 14:18:47 -0700632 selectionArgs = new String[] { refinedAddress };
633 } else {
Chen Mike01c75ba2010-11-24 14:21:31 +0100634 selection += " OR PHONE_NUMBERS_EQUAL(address, ?, " +
635 (mUseStrictPhoneNumberComparation ? 1 : 0) + ")";
Wei Huangc818d632009-09-28 14:18:47 -0700636 selectionArgs = new String[] { refinedAddress, refinedAddress };
637 }
638
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800639 Cursor cursor = null;
640
641 try {
642 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
643 cursor = db.query(
644 "canonical_addresses", ID_PROJECTION,
645 selection, selectionArgs, null, null, null);
646
647 if (cursor.getCount() == 0) {
648 ContentValues contentValues = new ContentValues(1);
649 contentValues.put(CanonicalAddressesColumns.ADDRESS, refinedAddress);
650
651 db = mOpenHelper.getWritableDatabase();
Wei Huang14595cb2010-04-08 11:33:26 -0700652 retVal = db.insert("canonical_addresses",
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800653 CanonicalAddressesColumns.ADDRESS, contentValues);
Wei Huang14595cb2010-04-08 11:33:26 -0700654
Wink Savilleea59f862010-10-11 21:26:31 -0700655 Log.d(LOG_TAG, "getSingleAddressId: insert new canonical_address for " +
656 /*address*/ "xxxxxx" + ", _id=" + retVal);
Wei Huang14595cb2010-04-08 11:33:26 -0700657
658 return retVal;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800659 }
660
661 if (cursor.moveToFirst()) {
Wei Huang14595cb2010-04-08 11:33:26 -0700662 retVal = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800663 }
664 } finally {
665 if (cursor != null) {
666 cursor.close();
667 }
668 }
669
Wei Huang14595cb2010-04-08 11:33:26 -0700670 return retVal;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800671 }
672
673 /**
674 * Return the canonical address IDs for these addresses.
675 */
676 private Set<Long> getAddressIds(List<String> addresses) {
677 Set<Long> result = new HashSet<Long>(addresses.size());
678
679 for (String address : addresses) {
680 if (!address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
681 long id = getSingleAddressId(address);
yanglv23c51f82014-08-08 14:50:21 +0800682 if (id != RESULT_FOR_ID_NOT_FOUND) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800683 result.add(id);
684 } else {
Wei Huang14595cb2010-04-08 11:33:26 -0700685 Log.e(LOG_TAG, "getAddressIds: address ID not found for " + address);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800686 }
687 }
688 }
689 return result;
690 }
691
692 /**
693 * Return a sorted array of the given Set of Longs.
694 */
695 private long[] getSortedSet(Set<Long> numbers) {
696 int size = numbers.size();
697 long[] result = new long[size];
698 int i = 0;
699
700 for (Long number : numbers) {
701 result[i++] = number;
702 }
Wei Huang14595cb2010-04-08 11:33:26 -0700703
704 if (size > 1) {
705 Arrays.sort(result);
706 }
707
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800708 return result;
709 }
710
711 /**
712 * Return a String of the numbers in the given array, in order,
713 * separated by spaces.
714 */
715 private String getSpaceSeparatedNumbers(long[] numbers) {
716 int size = numbers.length;
717 StringBuilder buffer = new StringBuilder();
718
719 for (int i = 0; i < size; i++) {
720 if (i != 0) {
721 buffer.append(' ');
722 }
723 buffer.append(numbers[i]);
724 }
725 return buffer.toString();
726 }
727
728 /**
729 * Insert a record for a new thread.
730 */
731 private void insertThread(String recipientIds, int numberOfRecipients) {
732 ContentValues values = new ContentValues(4);
733
734 long date = System.currentTimeMillis();
735 values.put(ThreadsColumns.DATE, date - date % 1000);
736 values.put(ThreadsColumns.RECIPIENT_IDS, recipientIds);
737 if (numberOfRecipients > 1) {
738 values.put(Threads.TYPE, Threads.BROADCAST_THREAD);
739 }
740 values.put(ThreadsColumns.MESSAGE_COUNT, 0);
741
Tom Taylor15156cd2012-11-15 14:15:46 -0800742 long result = mOpenHelper.getWritableDatabase().insert(TABLE_THREADS, null, values);
Wei Huang14595cb2010-04-08 11:33:26 -0700743 Log.d(LOG_TAG, "insertThread: created new thread_id " + result +
Wink Savilleea59f862010-10-11 21:26:31 -0700744 " for recipientIds " + /*recipientIds*/ "xxxxxxx");
Wei Huang14595cb2010-04-08 11:33:26 -0700745
Amith Yamasani43f9fb22014-09-10 15:56:47 -0700746 getContext().getContentResolver().notifyChange(MmsSms.CONTENT_URI, null, true,
747 UserHandle.USER_ALL);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800748 }
749
Wei Huang14595cb2010-04-08 11:33:26 -0700750 private static final String THREAD_QUERY =
751 "SELECT _id FROM threads " + "WHERE recipient_ids=?";
752
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800753 /**
754 * Return the thread ID for this list of
755 * recipients IDs. If no thread exists with this ID, create
756 * one and return it. Callers should always use
757 * Threads.getThreadId to access this information.
758 */
759 private synchronized Cursor getThreadId(List<String> recipients) {
Wei Huang14595cb2010-04-08 11:33:26 -0700760 Set<Long> addressIds = getAddressIds(recipients);
761 String recipientIds = "";
Wei Huangc818d632009-09-28 14:18:47 -0700762
Tom Taylor59269962012-05-09 14:42:35 -0700763 if (addressIds.size() == 0) {
764 Log.e(LOG_TAG, "getThreadId: NO receipients specified -- NOT creating thread",
765 new Exception());
766 return null;
767 } else if (addressIds.size() == 1) {
768 // optimize for size==1, which should be most of the cases
Wei Huang14595cb2010-04-08 11:33:26 -0700769 for (Long addressId : addressIds) {
770 recipientIds = Long.toString(addressId);
771 }
772 } else {
773 recipientIds = getSpaceSeparatedNumbers(getSortedSet(addressIds));
Tom Taylorb4ac04f2009-07-14 14:19:08 -0700774 }
Wei Huang14595cb2010-04-08 11:33:26 -0700775
776 if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
Wink Savilleea59f862010-10-11 21:26:31 -0700777 Log.d(LOG_TAG, "getThreadId: recipientIds (selectionArgs) =" +
778 /*recipientIds*/ "xxxxxxx");
Wei Huang14595cb2010-04-08 11:33:26 -0700779 }
780
781 String[] selectionArgs = new String[] { recipientIds };
Tom Taylor15156cd2012-11-15 14:15:46 -0800782
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800783 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Tom Taylor15156cd2012-11-15 14:15:46 -0800784 db.beginTransaction();
785 Cursor cursor = null;
786 try {
787 // Find the thread with the given recipients
Wei Huang14595cb2010-04-08 11:33:26 -0700788 cursor = db.rawQuery(THREAD_QUERY, selectionArgs);
Tom Taylor15156cd2012-11-15 14:15:46 -0800789
790 if (cursor.getCount() == 0) {
791 // No thread with those recipients exists, so create the thread.
792 cursor.close();
793
794 Log.d(LOG_TAG, "getThreadId: create new thread_id for recipients " +
795 /*recipients*/ "xxxxxxxx");
796 insertThread(recipientIds, recipients.size());
797
798 // The thread was just created, now find it and return it.
799 cursor = db.rawQuery(THREAD_QUERY, selectionArgs);
800 }
801 db.setTransactionSuccessful();
802 } catch (Throwable ex) {
803 Log.e(LOG_TAG, ex.getMessage(), ex);
804 } finally {
805 db.endTransaction();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800806 }
Mark Wagner6a917902010-05-03 12:52:23 -0700807
Tom Taylor15156cd2012-11-15 14:15:46 -0800808 if (cursor != null && cursor.getCount() > 1) {
Wei Huang14595cb2010-04-08 11:33:26 -0700809 Log.w(LOG_TAG, "getThreadId: why is cursorCount=" + cursor.getCount());
Tom Taylor69e6ffa2009-07-08 15:19:01 -0700810 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800811 return cursor;
812 }
813
814 private static String concatSelections(String selection1, String selection2) {
815 if (TextUtils.isEmpty(selection1)) {
816 return selection2;
817 } else if (TextUtils.isEmpty(selection2)) {
818 return selection1;
819 } else {
820 return selection1 + " AND " + selection2;
821 }
822 }
823
824 /**
825 * If a null projection is given, return the union of all columns
826 * in both the MMS and SMS messages tables. Otherwise, return the
827 * given projection.
828 */
829 private static String[] handleNullMessageProjection(
830 String[] projection) {
831 return projection == null ? UNION_COLUMNS : projection;
832 }
833
834 /**
835 * If a null projection is given, return the set of all columns in
836 * the threads table. Otherwise, return the given projection.
837 */
838 private static String[] handleNullThreadsProjection(
839 String[] projection) {
840 return projection == null ? THREADS_COLUMNS : projection;
841 }
842
843 /**
844 * If a null sort order is given, return "normalized_date ASC".
845 * Otherwise, return the given sort order.
846 */
847 private static String handleNullSortOrder (String sortOrder) {
848 return sortOrder == null ? "normalized_date ASC" : sortOrder;
849 }
850
851 /**
852 * Return existing threads in the database.
853 */
854 private Cursor getSimpleConversations(String[] projection, String selection,
855 String[] selectionArgs, String sortOrder) {
Tom Taylor15156cd2012-11-15 14:15:46 -0800856 return mOpenHelper.getReadableDatabase().query(TABLE_THREADS, projection,
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800857 selection, selectionArgs, null, null, " date DESC");
858 }
859
860 /**
861 * Return the thread which has draft in both MMS and SMS.
862 *
863 * Use this query:
864 *
865 * SELECT ...
866 * FROM (SELECT _id, thread_id, ...
867 * FROM pdu
868 * WHERE msg_box = 3 AND ...
869 * UNION
870 * SELECT _id, thread_id, ...
871 * FROM sms
872 * WHERE type = 3 AND ...
873 * )
874 * ;
875 */
876 private Cursor getDraftThread(String[] projection, String selection,
Ye Wen72f13552015-03-10 14:17:13 -0700877 String sortOrder, String smsTable, String pduTable) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800878 String[] innerProjection = new String[] {BaseColumns._ID, Conversations.THREAD_ID};
879 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
880 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
881
Ye Wen72f13552015-03-10 14:17:13 -0700882 mmsQueryBuilder.setTables(pduTable);
883 smsQueryBuilder.setTables(smsTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800884
885 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
886 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection,
887 MMS_COLUMNS, 1, "mms",
888 concatSelections(selection, Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_DRAFTS),
Tom Taylorf008a612011-05-13 13:49:34 -0700889 null, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800890 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
891 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection,
892 SMS_COLUMNS, 1, "sms",
893 concatSelections(selection, Sms.TYPE + "=" + Sms.MESSAGE_TYPE_DRAFT),
Tom Taylorf008a612011-05-13 13:49:34 -0700894 null, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800895 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
896
897 unionQueryBuilder.setDistinct(true);
898
899 String unionQuery = unionQueryBuilder.buildUnionQuery(
900 new String[] { mmsSubQuery, smsSubQuery }, null, null);
901
902 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
903
904 outerQueryBuilder.setTables("(" + unionQuery + ")");
905
906 String outerQuery = outerQueryBuilder.buildQuery(
Tom Taylorf008a612011-05-13 13:49:34 -0700907 projection, null, null, null, sortOrder, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800908
909 return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
910 }
911
912 /**
913 * Return the most recent message in each conversation in both MMS
914 * and SMS.
915 *
916 * Use this query:
917 *
918 * SELECT ...
919 * FROM (SELECT thread_id AS tid, date * 1000 AS normalized_date, ...
920 * FROM pdu
921 * WHERE msg_box != 3 AND ...
922 * GROUP BY thread_id
923 * HAVING date = MAX(date)
924 * UNION
925 * SELECT thread_id AS tid, date AS normalized_date, ...
926 * FROM sms
927 * WHERE ...
928 * GROUP BY thread_id
929 * HAVING date = MAX(date))
930 * GROUP BY tid
931 * HAVING normalized_date = MAX(normalized_date);
932 *
933 * The msg_box != 3 comparisons ensure that we don't include draft
934 * messages.
935 */
936 private Cursor getConversations(String[] projection, String selection,
Ye Wen72f13552015-03-10 14:17:13 -0700937 String sortOrder, String smsTable, String pduTable) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800938 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
939 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
940
Ye Wen72f13552015-03-10 14:17:13 -0700941 mmsQueryBuilder.setTables(pduTable);
942 smsQueryBuilder.setTables(smsTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800943
944 String[] columns = handleNullMessageProjection(projection);
945 String[] innerMmsProjection = makeProjectionWithDateAndThreadId(
946 UNION_COLUMNS, 1000);
947 String[] innerSmsProjection = makeProjectionWithDateAndThreadId(
948 UNION_COLUMNS, 1);
949 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
950 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
951 MMS_COLUMNS, 1, "mms",
Tom Taylorf008a612011-05-13 13:49:34 -0700952 concatSelections(selection, MMS_CONVERSATION_CONSTRAINT),
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800953 "thread_id", "date = MAX(date)");
954 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
955 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection,
956 SMS_COLUMNS, 1, "sms",
Tom Taylorf008a612011-05-13 13:49:34 -0700957 concatSelections(selection, SMS_CONVERSATION_CONSTRAINT),
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800958 "thread_id", "date = MAX(date)");
959 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
960
961 unionQueryBuilder.setDistinct(true);
962
963 String unionQuery = unionQueryBuilder.buildUnionQuery(
964 new String[] { mmsSubQuery, smsSubQuery }, null, null);
965
966 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
967
968 outerQueryBuilder.setTables("(" + unionQuery + ")");
969
970 String outerQuery = outerQueryBuilder.buildQuery(
Tom Taylorf008a612011-05-13 13:49:34 -0700971 columns, null, "tid",
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800972 "normalized_date = MAX(normalized_date)", sortOrder, null);
973
974 return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
975 }
976
977 /**
Tom Taylorf0a7f152009-08-31 17:56:47 -0700978 * Return the first locked message found in the union of MMS
979 * and SMS messages.
980 *
981 * Use this query:
982 *
Tom Taylor816e9342009-09-03 17:09:03 -0700983 * SELECT _id FROM pdu GROUP BY _id HAVING locked=1 UNION SELECT _id FROM sms GROUP
984 * BY _id HAVING locked=1 LIMIT 1
Tom Taylorf0a7f152009-08-31 17:56:47 -0700985 *
Tom Taylor816e9342009-09-03 17:09:03 -0700986 * We limit by 1 because we're only interested in knowing if
Tom Taylorf0a7f152009-08-31 17:56:47 -0700987 * there is *any* locked message, not the actual messages themselves.
988 */
989 private Cursor getFirstLockedMessage(String[] projection, String selection,
Ye Wen72f13552015-03-10 14:17:13 -0700990 String sortOrder, String smsTable, String pduTable) {
Tom Taylorf0a7f152009-08-31 17:56:47 -0700991 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
992 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
993
Ye Wen72f13552015-03-10 14:17:13 -0700994 mmsQueryBuilder.setTables(pduTable);
995 smsQueryBuilder.setTables(smsTable);
Tom Taylorf0a7f152009-08-31 17:56:47 -0700996
Tom Taylor816e9342009-09-03 17:09:03 -0700997 String[] idColumn = new String[] { BaseColumns._ID };
998
Tom Taylorf008a612011-05-13 13:49:34 -0700999 // NOTE: buildUnionSubQuery *ignores* selectionArgs
Tom Taylorf0a7f152009-08-31 17:56:47 -07001000 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
Tom Taylor816e9342009-09-03 17:09:03 -07001001 MmsSms.TYPE_DISCRIMINATOR_COLUMN, idColumn,
1002 null, 1, "mms",
Tom Taylorf008a612011-05-13 13:49:34 -07001003 selection,
Tom Taylor816e9342009-09-03 17:09:03 -07001004 BaseColumns._ID, "locked=1");
1005
Tom Taylorf0a7f152009-08-31 17:56:47 -07001006 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
Tom Taylor816e9342009-09-03 17:09:03 -07001007 MmsSms.TYPE_DISCRIMINATOR_COLUMN, idColumn,
1008 null, 1, "sms",
Tom Taylorf008a612011-05-13 13:49:34 -07001009 selection,
Tom Taylor816e9342009-09-03 17:09:03 -07001010 BaseColumns._ID, "locked=1");
1011
Tom Taylorf0a7f152009-08-31 17:56:47 -07001012 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
1013
1014 unionQueryBuilder.setDistinct(true);
1015
1016 String unionQuery = unionQueryBuilder.buildUnionQuery(
Tom Taylor816e9342009-09-03 17:09:03 -07001017 new String[] { mmsSubQuery, smsSubQuery }, null, "1");
Tom Taylorf0a7f152009-08-31 17:56:47 -07001018
Tom Taylor816e9342009-09-03 17:09:03 -07001019 Cursor cursor = mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
Tom Taylorf0a7f152009-08-31 17:56:47 -07001020
1021 if (DEBUG) {
Tom Taylor816e9342009-09-03 17:09:03 -07001022 Log.v("MmsSmsProvider", "getFirstLockedMessage query: " + unionQuery);
1023 Log.v("MmsSmsProvider", "cursor count: " + cursor.getCount());
Tom Taylorf0a7f152009-08-31 17:56:47 -07001024 }
Tom Taylor816e9342009-09-03 17:09:03 -07001025 return cursor;
Tom Taylorf0a7f152009-08-31 17:56:47 -07001026 }
1027
1028 /**
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001029 * Return every message in each conversation in both MMS
1030 * and SMS.
1031 */
1032 private Cursor getCompleteConversations(String[] projection,
Ye Wen72f13552015-03-10 14:17:13 -07001033 String selection, String sortOrder, String smsTable, String pduTable) {
1034 String unionQuery = buildConversationQuery(projection, selection, sortOrder, smsTable,
1035 pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001036
1037 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
1038 }
1039
1040 /**
1041 * Add normalized date and thread_id to the list of columns for an
1042 * inner projection. This is necessary so that the outer query
1043 * can have access to these columns even if the caller hasn't
1044 * requested them in the result.
1045 */
1046 private String[] makeProjectionWithDateAndThreadId(
1047 String[] projection, int dateMultiple) {
1048 int projectionSize = projection.length;
1049 String[] result = new String[projectionSize + 2];
1050
1051 result[0] = "thread_id AS tid";
1052 result[1] = "date * " + dateMultiple + " AS normalized_date";
1053 for (int i = 0; i < projectionSize; i++) {
1054 result[i + 2] = projection[i];
1055 }
1056 return result;
1057 }
1058
1059 /**
1060 * Return the union of MMS and SMS messages for this thread ID.
1061 */
1062 private Cursor getConversationMessages(
1063 String threadIdString, String[] projection, String selection,
Ye Wen72f13552015-03-10 14:17:13 -07001064 String sortOrder, String smsTable, String pduTable) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001065 try {
1066 Long.parseLong(threadIdString);
1067 } catch (NumberFormatException exception) {
1068 Log.e(LOG_TAG, "Thread ID must be a Long.");
1069 return null;
1070 }
1071
1072 String finalSelection = concatSelections(
1073 selection, "thread_id = " + threadIdString);
Ye Wen72f13552015-03-10 14:17:13 -07001074 String unionQuery = buildConversationQuery(projection, finalSelection, sortOrder, smsTable,
1075 pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001076
1077 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
1078 }
1079
1080 /**
Mengjun Leng6709d702014-09-23 12:11:00 +08001081 * Return the union of MMS and SMS messages in one mailbox.
1082 */
1083 private Cursor getMailboxMessages(String mailboxId, String[] projection,
1084 String selection, String[] selectionArgs, String sortOrder,
1085 boolean read, String pduTable) {
1086 try {
1087 Integer.parseInt(mailboxId);
1088 } catch (NumberFormatException exception) {
1089 Log.e(LOG_TAG, "mailboxId must be a Long.");
1090 return null;
1091 }
1092 String unionQuery = buildMailboxMsgQuery(mailboxId, projection,
1093 selection, selectionArgs, sortOrder, read, pduTable);
1094
1095 if (DEBUG) {
1096 Log.w(LOG_TAG, "getMailboxMessages : unionQuery =" + unionQuery);
1097 }
1098
1099 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery,
1100 EMPTY_STRING_ARRAY);
1101 }
1102
kaiyizf48e42c2014-10-18 12:13:41 +08001103 private static String appendSmsSelection(String selection) {
1104 if (!isMmsSelection(selection)) {
1105 return appendSelection(selection);
1106 }
1107 return "";
1108 }
1109
1110 private static String appendMmsSelection(String selection) {
1111 if (!isSmsSelection(selection)) {
1112 return appendSelection(selection);
1113 }
1114 return "";
1115 }
1116
1117 private static String appendSelection(String selection) {
1118 return TextUtils.isEmpty(selection) ? "" : " AND " + selection;
1119 }
1120
1121 private static boolean isSmsSelection(String selection) {
1122 return !TextUtils.isEmpty(selection) && selection.contains("sms.");
1123 }
1124
1125 private static boolean isMmsSelection(String selection) {
1126 return !TextUtils.isEmpty(selection) && selection.contains("pdu.");
1127 }
1128
Mengjun Leng6709d702014-09-23 12:11:00 +08001129 private static String buildMailboxMsgQuery(String mailboxId,
1130 String[] projection, String selection, String[] selectionArgs,
1131 String sortOrder, boolean read, String pduTable) {
1132 String[] mmsProjection = createMmsMailboxProjection(projection);
1133 String[] smsProjection = createSmsMailboxProjection(projection);
1134
1135 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
1136 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
1137
1138 mmsQueryBuilder.setDistinct(true);
1139 smsQueryBuilder.setDistinct(true);
1140 mmsQueryBuilder.setTables("threads, " + joinPduAndPendingMsgTables(pduTable));
1141 smsQueryBuilder.setTables(SmsProvider.TABLE_SMS + ", threads ");
1142
1143 String[] smsColumns = handleNullMessageProjection(smsProjection);
1144 String[] mmsColumns = handleNullMessageProjection(mmsProjection);
1145 String[] innerMmsProjection = makeMmsProjectionWithNormalizedDate(
1146 mmsColumns, 1000);
1147 String[] innerSmsProjection = makeSmsProjectionWithNormalizedDate(
1148 smsColumns, 1);
1149
1150 Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);
1151 columnsPresentInTable.add("pdu._id AS _id");
1152 columnsPresentInTable.add("pdu.date AS date");
1153 columnsPresentInTable.add("pdu.read AS read");
1154 columnsPresentInTable.add("pdu.sub_id AS sub_id");
1155 columnsPresentInTable.add("recipient_ids");
1156
1157 columnsPresentInTable.add(PendingMessages.ERROR_TYPE);
1158 String compare = " = ";
1159 int boxidInt = Integer.parseInt(mailboxId);
1160 if (boxidInt >= 4) {
1161 compare = " >= ";
1162 }
1163
kaiyizf48e42c2014-10-18 12:13:41 +08001164 String appendSmsSelection = appendSmsSelection(selection);
1165 String appendMmsSelection = appendMmsSelection(selection);
1166 String mmsSelection = Mms.MESSAGE_BOX + compare + mailboxId
1167 + " AND thread_id = threads._id AND m_type != "
yanglvbae8f912015-09-29 16:54:57 +08001168 + PduHeaders.MESSAGE_TYPE_DELIVERY_IND + appendMmsSelection;
kaiyizf48e42c2014-10-18 12:13:41 +08001169 String smsSelection = "(sms." + Sms.TYPE + compare + mailboxId
1170 + " AND thread_id = threads._id" + appendSmsSelection
1171 + ")" + " OR (sms." + Sms.TYPE + compare + mailboxId
1172 + " AND thread_id ISNULL " + appendSmsSelection
1173 + ")";
Mengjun Leng6709d702014-09-23 12:11:00 +08001174
1175 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
1176 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
1177 columnsPresentInTable, 0, "mms", mmsSelection, selectionArgs,
1178 null, null);
1179
1180 Set<String> columnsPresentInSmsTable = new HashSet<String>(SMS_COLUMNS);
1181 columnsPresentInSmsTable.add("sms._id AS _id");
1182 columnsPresentInSmsTable.add("sms.date AS date");
1183 columnsPresentInSmsTable.add("sms.read AS read");
1184 columnsPresentInSmsTable.add("sms.type AS type");
1185 columnsPresentInSmsTable.add("sms.sub_id AS sub_id");
1186 columnsPresentInSmsTable.add("recipient_ids");
1187
1188 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
1189 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection,
1190 columnsPresentInSmsTable, 0, "sms", smsSelection,
1191 selectionArgs, null, null);
1192
1193 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
1194 String unionQuery = null;
kaiyizf48e42c2014-10-18 12:13:41 +08001195 if (isMmsSelection(selection)) {
1196 unionQuery = mmsSubQuery;
1197 } else {
1198 unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {
1199 mmsSubQuery, smsSubQuery
1200 }, null, null);
1201 }
Mengjun Leng6709d702014-09-23 12:11:00 +08001202 if (DEBUG) {
1203 Log.w(LOG_TAG, "buildMailboxMsgQuery : unionQuery = " + unionQuery);
1204 }
1205
1206 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
1207 outerQueryBuilder.setTables("(" + unionQuery + ")");
1208
1209 return outerQueryBuilder.buildQuery(projection, null, null, null, null,
1210 sortOrder, null);
1211 }
1212
1213 /**
yanglv89620c02014-08-05 10:58:58 +08001214 * Return the SMS messages count on phone
1215 */
1216 private Cursor getAllMessagesCount() {
1217 String unionQuery = "select sum(a) AS count, 1 AS _id "
1218 + "from (" + "select count(sms._id) as a, 2 AS b from sms, threads"
1219 + " where thread_id NOTNULL AND thread_id = threads._id)";
1220
1221 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
1222 }
1223
1224 /**
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001225 * Return the union of MMS and SMS messages whose recipients
1226 * included this phone number.
1227 *
1228 * Use this query:
1229 *
1230 * SELECT ...
whliang670c4952012-11-06 15:26:15 +08001231 * FROM pdu, (SELECT msg_id AS address_msg_id
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001232 * FROM addr
Wei Huangc818d632009-09-28 14:18:47 -07001233 * WHERE (address='<phoneNumber>' OR
1234 * PHONE_NUMBERS_EQUAL(addr.address, '<phoneNumber>', 1/0)))
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001235 * AS matching_addresses
whliang670c4952012-11-06 15:26:15 +08001236 * WHERE pdu._id = matching_addresses.address_msg_id
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001237 * UNION
1238 * SELECT ...
1239 * FROM sms
Wei Huangc818d632009-09-28 14:18:47 -07001240 * WHERE (address='<phoneNumber>' OR PHONE_NUMBERS_EQUAL(sms.address, '<phoneNumber>', 1/0));
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001241 */
1242 private Cursor getMessagesByPhoneNumber(
1243 String phoneNumber, String[] projection, String selection,
Ye Wen72f13552015-03-10 14:17:13 -07001244 String sortOrder, String smsTable, String pduTable) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001245 String escapedPhoneNumber = DatabaseUtils.sqlEscapeString(phoneNumber);
1246 String finalMmsSelection =
1247 concatSelections(
1248 selection,
Ye Wen72f13552015-03-10 14:17:13 -07001249 pduTable + "._id = matching_addresses.address_msg_id");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001250 String finalSmsSelection =
1251 concatSelections(
1252 selection,
Wei Huangc818d632009-09-28 14:18:47 -07001253 "(address=" + escapedPhoneNumber + " OR PHONE_NUMBERS_EQUAL(address, " +
Daisuke Miyakawa845a9162009-09-19 19:42:14 -07001254 escapedPhoneNumber +
Wei Huangc818d632009-09-28 14:18:47 -07001255 (mUseStrictPhoneNumberComparation ? ", 1))" : ", 0))"));
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001256 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
1257 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
1258
1259 mmsQueryBuilder.setDistinct(true);
1260 smsQueryBuilder.setDistinct(true);
1261 mmsQueryBuilder.setTables(
Ye Wen72f13552015-03-10 14:17:13 -07001262 pduTable +
whliang670c4952012-11-06 15:26:15 +08001263 ", (SELECT msg_id AS address_msg_id " +
Wei Huangc818d632009-09-28 14:18:47 -07001264 "FROM addr WHERE (address=" + escapedPhoneNumber +
1265 " OR PHONE_NUMBERS_EQUAL(addr.address, " +
Daisuke Miyakawa845a9162009-09-19 19:42:14 -07001266 escapedPhoneNumber +
Wei Huangc818d632009-09-28 14:18:47 -07001267 (mUseStrictPhoneNumberComparation ? ", 1))) " : ", 0))) ") +
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001268 "AS matching_addresses");
Ye Wen72f13552015-03-10 14:17:13 -07001269 smsQueryBuilder.setTables(smsTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001270
1271 String[] columns = handleNullMessageProjection(projection);
1272 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
1273 MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, MMS_COLUMNS,
Tom Taylorf008a612011-05-13 13:49:34 -07001274 0, "mms", finalMmsSelection, null, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001275 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
1276 MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, SMS_COLUMNS,
Tom Taylorf008a612011-05-13 13:49:34 -07001277 0, "sms", finalSmsSelection, null, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001278 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
1279
1280 unionQueryBuilder.setDistinct(true);
1281
1282 String unionQuery = unionQueryBuilder.buildUnionQuery(
1283 new String[] { mmsSubQuery, smsSubQuery }, sortOrder, null);
1284
1285 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
1286 }
1287
1288 /**
1289 * Return the conversation of certain thread ID.
1290 */
1291 private Cursor getConversationById(
1292 String threadIdString, String[] projection, String selection,
1293 String[] selectionArgs, String sortOrder) {
1294 try {
1295 Long.parseLong(threadIdString);
1296 } catch (NumberFormatException exception) {
1297 Log.e(LOG_TAG, "Thread ID must be a Long.");
1298 return null;
1299 }
1300
1301 String extraSelection = "_id=" + threadIdString;
1302 String finalSelection = concatSelections(selection, extraSelection);
1303 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
1304 String[] columns = handleNullThreadsProjection(projection);
1305
1306 queryBuilder.setDistinct(true);
Tom Taylor15156cd2012-11-15 14:15:46 -08001307 queryBuilder.setTables(TABLE_THREADS);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001308 return queryBuilder.query(
1309 mOpenHelper.getReadableDatabase(), columns, finalSelection,
1310 selectionArgs, sortOrder, null, null);
1311 }
1312
Ye Wen72f13552015-03-10 14:17:13 -07001313 private static String joinPduAndPendingMsgTables(String pduTable) {
1314 return pduTable + " LEFT JOIN " + TABLE_PENDING_MSG
1315 + " ON " + pduTable + "._id = pending_msgs.msg_id";
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001316 }
1317
Ye Wen72f13552015-03-10 14:17:13 -07001318 private static String[] createMmsProjection(String[] old, String pduTable) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001319 String[] newProjection = new String[old.length];
1320 for (int i = 0; i < old.length; i++) {
1321 if (old[i].equals(BaseColumns._ID)) {
Ye Wen72f13552015-03-10 14:17:13 -07001322 newProjection[i] = pduTable + "._id";
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001323 } else {
1324 newProjection[i] = old[i];
1325 }
1326 }
1327 return newProjection;
1328 }
1329
Mengjun Leng6709d702014-09-23 12:11:00 +08001330 private static String[] createMmsMailboxProjection(String[] old) {
1331 if (old == null) {
1332 return null;
1333 }
1334
1335 int length = old.length;
1336 String[] newProjection = new String[length];
1337 for (int i = 0; i < length; i++) {
1338 if (old[i].equals(BaseColumns._ID)) {
1339 newProjection[i] = "pdu._id AS _id";
1340 } else if (old[i].equals("date")) {
1341 newProjection[i] = "pdu.date AS date";
1342 } else if (old[i].equals("read")) {
1343 newProjection[i] = "pdu.read AS read";
1344 } else if (old[i].equals("sub_id")) {
1345 newProjection[i] = "pdu.sub_id AS sub_id";
1346 } else {
1347 newProjection[i] = old[i];
1348 }
1349 }
1350 return newProjection;
1351 }
1352
1353 private static String[] createSmsMailboxProjection(String[] old) {
1354 if (old == null) {
1355 return null;
1356 }
1357
1358 int length = old.length;
1359 String[] newProjection = new String[length];
1360 for (int i = 0; i < length; i++) {
1361 if (old[i].equals(BaseColumns._ID)) {
1362 newProjection[i] = "sms._id AS _id";
1363 } else if (old[i].equals("date")) {
1364 newProjection[i] = "sms.date AS date";
1365 } else if (old[i].equals("read")) {
1366 newProjection[i] = "sms.read AS read";
1367 } else if (old[i].equals("type")) {
1368 newProjection[i] = "sms.type AS type";
1369 } else if (old[i].equals("sub_id")) {
1370 newProjection[i] = "sms.sub_id AS sub_id";
1371 } else {
1372 newProjection[i] = old[i];
1373 }
1374 }
1375 return newProjection;
1376 }
1377
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001378 private Cursor getUndeliveredMessages(
1379 String[] projection, String selection, String[] selectionArgs,
Ye Wen72f13552015-03-10 14:17:13 -07001380 String sortOrder, String smsTable, String pduTable) {
1381 String[] mmsProjection = createMmsProjection(projection, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001382
1383 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
1384 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
1385
Ye Wen72f13552015-03-10 14:17:13 -07001386 mmsQueryBuilder.setTables(joinPduAndPendingMsgTables(pduTable));
1387 smsQueryBuilder.setTables(smsTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001388
1389 String finalMmsSelection = concatSelections(
1390 selection, Mms.MESSAGE_BOX + " = " + Mms.MESSAGE_BOX_OUTBOX);
1391 String finalSmsSelection = concatSelections(
1392 selection, "(" + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_OUTBOX
1393 + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_FAILED
1394 + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_QUEUED + ")");
1395
1396 String[] smsColumns = handleNullMessageProjection(projection);
1397 String[] mmsColumns = handleNullMessageProjection(mmsProjection);
1398 String[] innerMmsProjection = makeProjectionWithDateAndThreadId(
1399 mmsColumns, 1000);
1400 String[] innerSmsProjection = makeProjectionWithDateAndThreadId(
1401 smsColumns, 1);
1402
1403 Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);
Ye Wen72f13552015-03-10 14:17:13 -07001404 columnsPresentInTable.add(pduTable + "._id");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001405 columnsPresentInTable.add(PendingMessages.ERROR_TYPE);
1406 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
1407 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
Tom Taylorf008a612011-05-13 13:49:34 -07001408 columnsPresentInTable, 1, "mms", finalMmsSelection,
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001409 null, null);
1410 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
1411 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection,
Tom Taylorf008a612011-05-13 13:49:34 -07001412 SMS_COLUMNS, 1, "sms", finalSmsSelection,
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001413 null, null);
1414 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
1415
1416 unionQueryBuilder.setDistinct(true);
1417
1418 String unionQuery = unionQueryBuilder.buildUnionQuery(
1419 new String[] { smsSubQuery, mmsSubQuery }, null, null);
1420
1421 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
1422
1423 outerQueryBuilder.setTables("(" + unionQuery + ")");
1424
1425 String outerQuery = outerQueryBuilder.buildQuery(
Tom Taylorf008a612011-05-13 13:49:34 -07001426 smsColumns, null, null, null, sortOrder, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001427
1428 return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
1429 }
1430
1431 /**
1432 * Add normalized date to the list of columns for an inner
1433 * projection.
1434 */
1435 private static String[] makeProjectionWithNormalizedDate(
1436 String[] projection, int dateMultiple) {
1437 int projectionSize = projection.length;
1438 String[] result = new String[projectionSize + 1];
1439
1440 result[0] = "date * " + dateMultiple + " AS normalized_date";
1441 System.arraycopy(projection, 0, result, 1, projectionSize);
1442 return result;
1443 }
1444
Mengjun Leng6709d702014-09-23 12:11:00 +08001445 /**
1446 * Add normalized date to the list of columns for an inner
1447 * projection.
1448 */
1449 private static String[] makeSmsProjectionWithNormalizedDate(
1450 String[] projection, int dateMultiple) {
1451 int projectionSize = projection.length;
1452 String[] result = new String[projectionSize + 1];
1453
1454 result[0] = "sms.date * " + dateMultiple + " AS normalized_date";
1455 System.arraycopy(projection, 0, result, 1, projectionSize);
1456 return result;
1457 }
1458
1459 private static String[] makeMmsProjectionWithNormalizedDate(
1460 String[] projection, int dateMultiple) {
1461 int projectionSize = projection.length;
1462 String[] result = new String[projectionSize + 1];
1463
1464 result[0] = "pdu.date * " + dateMultiple + " AS normalized_date";
1465 System.arraycopy(projection, 0, result, 1, projectionSize);
1466 return result;
1467 }
1468
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001469 private static String buildConversationQuery(String[] projection,
Ye Wen72f13552015-03-10 14:17:13 -07001470 String selection, String sortOrder, String smsTable, String pduTable) {
1471 String[] mmsProjection = createMmsProjection(projection, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001472
1473 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
1474 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
1475
1476 mmsQueryBuilder.setDistinct(true);
1477 smsQueryBuilder.setDistinct(true);
Ye Wen72f13552015-03-10 14:17:13 -07001478 mmsQueryBuilder.setTables(joinPduAndPendingMsgTables(pduTable));
1479 smsQueryBuilder.setTables(smsTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001480
1481 String[] smsColumns = handleNullMessageProjection(projection);
1482 String[] mmsColumns = handleNullMessageProjection(mmsProjection);
1483 String[] innerMmsProjection = makeProjectionWithNormalizedDate(mmsColumns, 1000);
1484 String[] innerSmsProjection = makeProjectionWithNormalizedDate(smsColumns, 1);
1485
1486 Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);
Ye Wen72f13552015-03-10 14:17:13 -07001487 columnsPresentInTable.add(pduTable + "._id");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001488 columnsPresentInTable.add(PendingMessages.ERROR_TYPE);
1489
1490 String mmsSelection = concatSelections(selection,
1491 Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS);
1492 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
1493 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
1494 columnsPresentInTable, 0, "mms",
1495 concatSelections(mmsSelection, MMS_CONVERSATION_CONSTRAINT),
Tom Taylorf008a612011-05-13 13:49:34 -07001496 null, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001497 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
1498 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, SMS_COLUMNS,
1499 0, "sms", concatSelections(selection, SMS_CONVERSATION_CONSTRAINT),
Tom Taylorf008a612011-05-13 13:49:34 -07001500 null, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001501 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
1502
1503 unionQueryBuilder.setDistinct(true);
1504
1505 String unionQuery = unionQueryBuilder.buildUnionQuery(
1506 new String[] { smsSubQuery, mmsSubQuery },
1507 handleNullSortOrder(sortOrder), null);
1508
1509 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
1510
1511 outerQueryBuilder.setTables("(" + unionQuery + ")");
1512
1513 return outerQueryBuilder.buildQuery(
Tom Taylorf008a612011-05-13 13:49:34 -07001514 smsColumns, null, null, null, sortOrder, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001515 }
1516
1517 @Override
1518 public String getType(Uri uri) {
1519 return VND_ANDROID_DIR_MMS_SMS;
1520 }
1521
1522 @Override
1523 public int delete(Uri uri, String selection,
1524 String[] selectionArgs) {
1525 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1526 Context context = getContext();
1527 int affectedRows = 0;
Mark Wagnerf0a9e902009-06-19 16:00:13 -07001528
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001529 switch(URI_MATCHER.match(uri)) {
1530 case URI_CONVERSATIONS_MESSAGES:
1531 long threadId;
1532 try {
1533 threadId = Long.parseLong(uri.getLastPathSegment());
1534 } catch (NumberFormatException e) {
1535 Log.e(LOG_TAG, "Thread ID must be a long.");
1536 break;
1537 }
1538 affectedRows = deleteConversation(uri, selection, selectionArgs);
1539 MmsSmsDatabaseHelper.updateThread(db, threadId);
1540 break;
1541 case URI_CONVERSATIONS:
1542 affectedRows = MmsProvider.deleteMessages(context, db,
1543 selection, selectionArgs, uri)
1544 + db.delete("sms", selection, selectionArgs);
Tom Taylor87bfe142009-08-18 15:33:51 -07001545 // Intentionally don't pass the selection variable to updateAllThreads.
1546 // When we pass in "locked=0" there, the thread will get excluded from
1547 // the selection and not get updated.
1548 MmsSmsDatabaseHelper.updateAllThreads(db, null, null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001549 break;
1550 case URI_OBSOLETE_THREADS:
Tom Taylor15156cd2012-11-15 14:15:46 -08001551 affectedRows = db.delete(TABLE_THREADS,
Tom Taylor4b14c352012-06-01 16:44:08 -07001552 "_id NOT IN (SELECT DISTINCT thread_id FROM sms where thread_id NOT NULL " +
1553 "UNION SELECT DISTINCT thread_id FROM pdu where thread_id NOT NULL)", null);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001554 break;
1555 default:
Tom Taylorb91bcae2012-02-03 13:12:13 -08001556 throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001557 }
1558
1559 if (affectedRows > 0) {
Amith Yamasani43f9fb22014-09-10 15:56:47 -07001560 context.getContentResolver().notifyChange(MmsSms.CONTENT_URI, null, true,
1561 UserHandle.USER_ALL);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001562 }
1563 return affectedRows;
1564 }
1565
1566 /**
1567 * Delete the conversation with the given thread ID.
1568 */
1569 private int deleteConversation(Uri uri, String selection, String[] selectionArgs) {
1570 String threadId = uri.getLastPathSegment();
1571
1572 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1573 String finalSelection = concatSelections(selection, "thread_id = " + threadId);
1574 return MmsProvider.deleteMessages(getContext(), db, finalSelection,
1575 selectionArgs, uri)
1576 + db.delete("sms", finalSelection, selectionArgs);
1577 }
1578
1579 @Override
1580 public Uri insert(Uri uri, ContentValues values) {
Tom Taylorb91bcae2012-02-03 13:12:13 -08001581 if (URI_MATCHER.match(uri) == URI_PENDING_MSG) {
1582 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1583 long rowId = db.insert(TABLE_PENDING_MSG, null, values);
1584 return Uri.parse(uri + "/" + rowId);
1585 }
1586 throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001587 }
1588
1589 @Override
1590 public int update(Uri uri, ContentValues values,
1591 String selection, String[] selectionArgs) {
Ye Wene07acb92014-11-19 12:06:05 -08001592 final int callerUid = Binder.getCallingUid();
Ye Wen72f13552015-03-10 14:17:13 -07001593 final String callerPkg = getCallingPackage();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001594 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1595 int affectedRows = 0;
1596 switch(URI_MATCHER.match(uri)) {
1597 case URI_CONVERSATIONS_MESSAGES:
1598 String threadIdString = uri.getPathSegments().get(1);
1599 affectedRows = updateConversation(threadIdString, values,
Ye Wen72f13552015-03-10 14:17:13 -07001600 selection, selectionArgs, callerUid, callerPkg);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001601 break;
Wei Huang1ecf1922009-11-08 12:12:07 -08001602
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001603 case URI_PENDING_MSG:
1604 affectedRows = db.update(TABLE_PENDING_MSG, values, selection, null);
1605 break;
Wei Huang1ecf1922009-11-08 12:12:07 -08001606
1607 case URI_CANONICAL_ADDRESS: {
1608 String extraSelection = "_id=" + uri.getPathSegments().get(1);
1609 String finalSelection = TextUtils.isEmpty(selection)
1610 ? extraSelection : extraSelection + " AND " + selection;
1611
1612 affectedRows = db.update(TABLE_CANONICAL_ADDRESSES, values, finalSelection, null);
1613 break;
1614 }
1615
Ye Wen82fc72b2014-07-29 10:42:54 -07001616 case URI_CONVERSATIONS: {
1617 final ContentValues finalValues = new ContentValues(1);
1618 if (values.containsKey(Threads.ARCHIVED)) {
1619 // Only allow update archived
1620 finalValues.put(Threads.ARCHIVED, values.getAsBoolean(Threads.ARCHIVED));
1621 }
1622 affectedRows = db.update(TABLE_THREADS, finalValues, selection, selectionArgs);
1623 break;
1624 }
1625
kaiyizd7f15a12014-11-28 17:52:35 +08001626 case URI_UPDATE_THREAD:
1627 long threadId;
1628 try {
1629 threadId = Long.parseLong(uri.getLastPathSegment());
1630 } catch (NumberFormatException e) {
1631 Log.e(LOG_TAG, "Thread ID must be a long.");
1632 break;
1633 }
wangjing71ae0ad2015-04-13 16:36:01 +08001634 updateConversationUnread(db, threadId);
kaiyizd7f15a12014-11-28 17:52:35 +08001635 MmsSmsDatabaseHelper.updateThread(db, threadId);
1636 break;
1637
kaiyiza4238572014-08-09 15:07:18 +08001638 case URI_UPDATE_THREAD_DATE:
1639 MmsSmsDatabaseHelper.updateThreadsDate(db, selection, selectionArgs);
1640 break;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001641 default:
1642 throw new UnsupportedOperationException(
Tom Taylorb91bcae2012-02-03 13:12:13 -08001643 NO_DELETES_INSERTS_OR_UPDATES + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001644 }
1645
1646 if (affectedRows > 0) {
1647 getContext().getContentResolver().notifyChange(
Amith Yamasani43f9fb22014-09-10 15:56:47 -07001648 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001649 }
1650 return affectedRows;
1651 }
1652
wangjing71ae0ad2015-04-13 16:36:01 +08001653 private void updateConversationUnread(SQLiteDatabase db, long threadId) {
1654 db.execSQL(
1655 " UPDATE threads SET read = " +
1656 " CASE (SELECT COUNT(*) FROM (SELECT read FROM " +
1657 " sms WHERE read = 0 AND thread_id = " + threadId +
1658 " UNION ALL SELECT read FROM pdu WHERE read = 0 " +
1659 " AND thread_id = " + threadId +"))" +
1660 " WHEN 0 THEN 1 ELSE 0 END;");
1661 }
1662
Ye Wen72f13552015-03-10 14:17:13 -07001663 private int updateConversation(String threadIdString, ContentValues values, String selection,
1664 String[] selectionArgs, int callerUid, String callerPkg) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001665 try {
1666 Long.parseLong(threadIdString);
1667 } catch (NumberFormatException exception) {
1668 Log.e(LOG_TAG, "Thread ID must be a Long.");
1669 return 0;
Ye Wene07acb92014-11-19 12:06:05 -08001670
1671 }
1672 if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
1673 // CREATOR should not be changed by non-SYSTEM/PHONE apps
Ye Wen72f13552015-03-10 14:17:13 -07001674 Log.w(LOG_TAG, callerPkg + " tries to update CREATOR");
Ye Wene07acb92014-11-19 12:06:05 -08001675 // Sms.CREATOR and Mms.CREATOR are same. But let's do this
1676 // twice in case the names may differ in the future
1677 values.remove(Sms.CREATOR);
1678 values.remove(Mms.CREATOR);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001679 }
1680
1681 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1682 String finalSelection = concatSelections(selection, "thread_id=" + threadIdString);
1683 return db.update(MmsProvider.TABLE_PDU, values, finalSelection, selectionArgs)
1684 + db.update("sms", values, finalSelection, selectionArgs);
1685 }
1686
1687 /**
1688 * Construct Sets of Strings containing exactly the columns
1689 * present in each table. We will use this when constructing
1690 * UNION queries across the MMS and SMS tables.
1691 */
1692 private static void initializeColumnSets() {
1693 int commonColumnCount = MMS_SMS_COLUMNS.length;
1694 int mmsOnlyColumnCount = MMS_ONLY_COLUMNS.length;
1695 int smsOnlyColumnCount = SMS_ONLY_COLUMNS.length;
1696 Set<String> unionColumns = new HashSet<String>();
1697
1698 for (int i = 0; i < commonColumnCount; i++) {
1699 MMS_COLUMNS.add(MMS_SMS_COLUMNS[i]);
1700 SMS_COLUMNS.add(MMS_SMS_COLUMNS[i]);
1701 unionColumns.add(MMS_SMS_COLUMNS[i]);
1702 }
1703 for (int i = 0; i < mmsOnlyColumnCount; i++) {
1704 MMS_COLUMNS.add(MMS_ONLY_COLUMNS[i]);
1705 unionColumns.add(MMS_ONLY_COLUMNS[i]);
1706 }
1707 for (int i = 0; i < smsOnlyColumnCount; i++) {
1708 SMS_COLUMNS.add(SMS_ONLY_COLUMNS[i]);
1709 unionColumns.add(SMS_ONLY_COLUMNS[i]);
1710 }
1711
1712 int i = 0;
1713 for (String columnName : unionColumns) {
1714 UNION_COLUMNS[i++] = columnName;
1715 }
1716 }
Ye Wen86b8a2c2015-06-11 11:25:31 -07001717
1718 @Override
1719 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1720 // Dump default SMS app
1721 String defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(getContext());
1722 if (TextUtils.isEmpty(defaultSmsApp)) {
1723 defaultSmsApp = "None";
1724 }
1725 writer.println("Default SMS app: " + defaultSmsApp);
1726 }
yanglv23c51f82014-08-08 14:50:21 +08001727
1728 private Cursor getSearchMessages(Uri uri, SQLiteDatabase db) {
1729 int searchMode = Integer.parseInt(uri.getQueryParameter("search_mode"));
1730 String keyStr = uri.getQueryParameter("key_str");
1731 int matchWhole = Integer.parseInt(uri.getQueryParameter("match_whole"));
1732 if (DEBUG) Log.d(LOG_TAG, "getSearchMessages : searchMode =" + searchMode + ",keyStr="
1733 + keyStr + ",matchWhole=" + matchWhole);
1734
1735 String threadIdString = getThreadIdString(keyStr, matchWhole);
1736
1737 String smsQuery = getSmsQueryString(searchMode, matchWhole, threadIdString);
1738 String mmsQuery = getMmsQueryString(searchMode, matchWhole, threadIdString);
1739 String searchString = "%" + addEscapeCharacter(keyStr) + "%";
1740 String rawQuery = String.format(
1741 "%s UNION %s ORDER BY date DESC",
1742 smsQuery,
1743 mmsQuery);
1744 String[] strArray;
1745 if (searchMode == SEARCH_MODE_CONTENT
1746 || (searchMode == SEARCH_MODE_NUMBER && matchWhole == MATCH_BY_ADDRESS)) {
1747 strArray = new String[] {
1748 searchString, searchString
1749 };
1750 } else if (searchMode == SEARCH_MODE_SUBJECT) {
1751 rawQuery = String.format("%s ORDER BY date DESC", mmsQuery);
1752 strArray = new String[] {searchString};
1753 } else {
1754 strArray = EMPTY_STRING_ARRAY;
1755 }
1756 return db.rawQuery(rawQuery, strArray);
1757 }
1758
1759 private String getThreadIdString(String keyStr, int matchWhole) {
1760 if (matchWhole != MATCH_BY_THREAD_ID) {
1761 return "";
1762 }
1763 long[] addressIdSet = getSortedSet(getAddressIdsByAddressList(keyStr.split(",")));
1764 String threadIdString = getCommaSeparatedId(addressIdSet);
1765 if (TextUtils.isEmpty(threadIdString)) {
1766 threadIdString = DEFAULT_STRING_ZERO;
1767 }
1768 return threadIdString;
1769 }
1770
1771 private String addEscapeCharacter(String keyStr) {
1772 if (keyStr == null) {
1773 return keyStr;
1774 }
1775 if (keyStr.contains("%") ||
1776 keyStr.contains(String.valueOf(SEARCH_ESCAPE_CHARACTER))) {
1777 StringBuilder searchKeyStrBuilder = new StringBuilder();
1778 int keyStrLen = keyStr.length();
1779 for (int i = 0; i < keyStrLen; i++) {
1780 if (keyStr.charAt(i) == '%' ||
1781 keyStr.charAt(i) == SEARCH_ESCAPE_CHARACTER) {
1782 searchKeyStrBuilder.append(SEARCH_ESCAPE_CHARACTER);
1783 searchKeyStrBuilder.append(keyStr.charAt(i));
1784 continue;
1785 }
1786 searchKeyStrBuilder.append(keyStr.charAt(i));
1787 }
1788 return searchKeyStrBuilder.toString();
1789 }
1790 return keyStr;
1791 }
1792
1793 private Set<Long> getAddressIdsByAddressList(String[] addresses) {
1794 int count = addresses.length;
1795 Set<Long> result = new HashSet<Long>(count);
1796
1797 for (int i = 0; i < count; i++) {
1798 String address = addresses[i];
1799 if (address != null && !address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
1800 long id = getSingleThreadId(address);
1801 if (id != RESULT_FOR_ID_NOT_FOUND) {
1802 result.add(id);
1803 } else {
1804 Log.e(LOG_TAG, "Address ID not found for: " + address);
1805 }
1806 }
1807 }
1808 return result;
1809 }
1810
1811 private String getCommaSeparatedId(long[] addrIds) {
1812 int size = addrIds.length;
1813 StringBuilder buffer = new StringBuilder();
1814
1815 for (int i = 0; i < size; i++) {
1816 if (i != 0) {
1817 buffer.append(',');
1818 }
1819 buffer.append(getThreadIds(String.valueOf(addrIds[i])));
1820 }
1821 return buffer.toString();
1822 }
1823
1824 private long getSingleThreadId(String address) {
1825 boolean isEmail = Mms.isEmailAddress(address);
1826 String refinedAddress = isEmail ? address.toLowerCase() : address;
1827 String selection = "address=?";
1828 String[] selectionArgs;
1829
1830 if (isEmail) {
1831 selectionArgs = new String[] { refinedAddress };
1832 } else {
1833 selection += " OR " + String.format("PHONE_NUMBERS_EQUAL(address, ?, %d)",
1834 (mUseStrictPhoneNumberComparation ? 1 : 0));
1835 selectionArgs = new String[] { refinedAddress, refinedAddress };
1836 }
1837
1838 Cursor cursor = null;
1839
1840 try {
1841 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1842 cursor = db.query(
1843 "canonical_addresses", ID_PROJECTION,
1844 selection, selectionArgs, null, null, null);
1845
1846 if (cursor.getCount() == 0) {
1847 return RESULT_FOR_ID_NOT_FOUND;
1848 }
1849
1850 if (cursor.moveToFirst()) {
1851 return cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
1852 }
1853 } finally {
1854 if (cursor != null) {
1855 cursor.close();
1856 }
1857 }
1858
1859 return RESULT_FOR_ID_NOT_FOUND;
1860 }
1861
1862 private synchronized String getThreadIdByRecipientIds(String recipientIds) {
1863 String THREAD_QUERY = "SELECT _id FROM threads " +
1864 "WHERE recipient_ids = ?";
1865 String resultString = DEFAULT_STRING_ZERO;
1866
1867 if (DEBUG) {
1868 Log.v(LOG_TAG, "getThreadId THREAD_QUERY: " + THREAD_QUERY +
1869 ", recipientIds=" + recipientIds);
1870 }
1871 Cursor cursor = null;
1872 try {
1873 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1874 cursor = db.rawQuery(THREAD_QUERY, new String[] { recipientIds });
1875
1876 if (cursor == null || cursor.getCount() == 0) {
1877 return resultString;
1878 }
1879
1880 if (cursor.moveToFirst()) {
1881 resultString = String.valueOf(cursor.getLong(0));
1882 }
1883 } finally {
1884 if (cursor != null) {
1885 cursor.close();
1886 }
1887 }
1888
1889 return resultString;
1890 }
1891
1892 private String getSmsQueryString(int searchMode, int matchWhole, String threadIdString) {
1893 String smsQuery = "";
1894 if (searchMode == SEARCH_MODE_CONTENT) {
1895 smsQuery = String.format(
1896 "SELECT %s FROM sms WHERE (body LIKE ? ESCAPE '" +
1897 SEARCH_ESCAPE_CHARACTER + "') ",
1898 SMS_PROJECTION);
1899 } else if (searchMode == SEARCH_MODE_NUMBER && matchWhole == MATCH_BY_ADDRESS) {
1900 smsQuery = String.format(
1901 "SELECT %s FROM sms WHERE (address LIKE ?)",
1902 SMS_PROJECTION);
1903 } else if (searchMode == SEARCH_MODE_NUMBER && matchWhole == MATCH_BY_THREAD_ID) {
1904 smsQuery = String.format(
1905 "SELECT %s FROM sms WHERE (thread_id in (%s))",
1906 SMS_PROJECTION,
1907 threadIdString);
1908 }
1909 return smsQuery;
1910 }
1911
1912 private String getMmsQueryString(int searchMode, int matchWhole, String threadIdString) {
1913 String mmsQuery = "";
1914 if (searchMode == SEARCH_MODE_CONTENT) {
kaiyize5a13fa2014-09-15 15:36:54 +08001915 mmsQuery = String.format(Locale.US,
yanglv23c51f82014-08-08 14:50:21 +08001916 "SELECT %s FROM pdu,part,addr WHERE ((part.mid=pdu._id) AND " +
1917 "(addr.msg_id=pdu._id) AND " +
1918 "(addr.type=%d) AND " +
1919 "(part.ct='text/plain') AND " +
1920 "(body like ? escape '" + SEARCH_ESCAPE_CHARACTER + "')) GROUP BY pdu._id",
1921 MMS_PROJECTION,
1922 PduHeaders.TO);
1923 } else if (searchMode == SEARCH_MODE_SUBJECT) {
1924 mmsQuery = String.format(
1925 "SELECT %s FROM pdu,addr WHERE (" +
1926 "(addr.msg_id = pdu._id) AND (addr.type=%d) AND " +
1927 "(body like ? escape '" + SEARCH_ESCAPE_CHARACTER + "')) GROUP BY pdu._id",
1928 MMS_PROJECTION_FOR_SUBJECT_SEARCH,
1929 PduHeaders.TO);
1930 } else if (searchMode == SEARCH_MODE_NUMBER && matchWhole == MATCH_BY_ADDRESS) {
1931 mmsQuery = String.format(
1932 "SELECT %s FROM pdu,addr WHERE (" +
1933 "(addr.msg_id=pdu._id) AND " +
1934 "(address like ?)) GROUP BY pdu._id",
1935 MMS_PROJECTION_FOR_NUMBER_SEARCH);
1936 } else if (searchMode == SEARCH_MODE_NUMBER && matchWhole == MATCH_BY_THREAD_ID) {
kaiyize5a13fa2014-09-15 15:36:54 +08001937 mmsQuery = String.format(Locale.US,
yanglv23c51f82014-08-08 14:50:21 +08001938 "SELECT %s FROM pdu,addr WHERE (" +
1939 "(thread_id in (%s)) AND " +
1940 "(addr.msg_id = pdu._id) AND " +
1941 "(addr.type=%d))",
1942 MMS_PROJECTION_FOR_NUMBER_SEARCH,
1943 threadIdString,
1944 PduHeaders.TO);
1945 }
1946 return mmsQuery;
1947 }
1948
1949 private synchronized String getThreadIds(String recipientIds) {
1950 String THREAD_QUERY = "SELECT _id FROM threads " +
1951 "WHERE recipient_ids = ? or recipient_ids like '" + recipientIds
1952 + " %' or recipient_ids like '% " + recipientIds
1953 + "' or recipient_ids like '% " + recipientIds + " %'";
1954 String resultString = DEFAULT_STRING_ZERO;
1955 StringBuilder buffer = new StringBuilder();
1956
1957 if (DEBUG) {
1958 Log.v(LOG_TAG, "getThreadId THREAD_QUERY: " + THREAD_QUERY +
1959 ", recipientIds=" + recipientIds);
1960 }
1961 Cursor cursor = null;
1962 try {
1963 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1964 cursor = db.rawQuery(THREAD_QUERY, new String[] {
1965 recipientIds
1966 });
1967
1968 if (cursor == null || cursor.getCount() == 0) {
1969 return resultString;
1970 }
1971 int i = 0;
1972 while (cursor.moveToNext()) {
1973 if (i != 0) {
1974 buffer.append(',');
1975 }
1976 buffer.append(String.valueOf(cursor.getLong(0)));
1977 i++;
1978 }
1979 resultString = buffer.toString();
1980
1981 } finally {
1982 if (cursor != null) {
1983 cursor.close();
1984 }
1985 }
1986
1987 return resultString;
1988 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -08001989}