| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.providers.telephony; |
| |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import android.content.ContentProvider; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.UriMatcher; |
| import android.database.Cursor; |
| import android.database.DatabaseUtils; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.database.sqlite.SQLiteOpenHelper; |
| import android.database.sqlite.SQLiteQueryBuilder; |
| import android.net.Uri; |
| import android.provider.BaseColumns; |
| import android.provider.Telephony.CanonicalAddressesColumns; |
| import android.provider.Telephony.Mms; |
| import android.provider.Telephony.MmsSms; |
| import android.provider.Telephony.Sms; |
| import android.provider.Telephony.Threads; |
| import android.provider.Telephony.ThreadsColumns; |
| import android.provider.Telephony.MmsSms.PendingMessages; |
| import android.provider.Telephony.Sms.Conversations; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.google.android.mms.pdu.PduHeaders; |
| |
| /** |
| * This class provides the ability to query the MMS and SMS databases |
| * at the same time, mixing messages from both in a single thread |
| * (A.K.A. conversation). |
| * |
| * A virtual column, MmsSms.TYPE_DISCRIMINATOR_COLUMN, may be |
| * requested in the projection for a query. Its value is either "mms" |
| * or "sms", depending on whether the message represented by the row |
| * is an MMS message or an SMS message, respectively. |
| * |
| * This class also provides the ability to find out what addresses |
| * participated in a particular thread. It doesn't support updates |
| * for either of these. |
| * |
| * This class provides a way to allocate and retrieve thread IDs. |
| * This is done atomically through a query. There is no insert URI |
| * for this. |
| * |
| * Finally, this class provides a way to delete or update all messages |
| * in a thread. |
| */ |
| public class MmsSmsProvider extends ContentProvider { |
| private static final UriMatcher URI_MATCHER = |
| new UriMatcher(UriMatcher.NO_MATCH); |
| private static final String LOG_TAG = "MmsSmsProvider"; |
| private static final boolean DEBUG = false; |
| |
| private static final String NO_DELETES_INSERTS_OR_UPDATES = |
| "MmsSmsProvider does not support deletes, inserts, or updates for this URI."; |
| private static final int URI_CONVERSATIONS = 0; |
| private static final int URI_CONVERSATIONS_MESSAGES = 1; |
| private static final int URI_CONVERSATIONS_RECIPIENTS = 2; |
| private static final int URI_MESSAGES_BY_PHONE = 3; |
| private static final int URI_THREAD_ID = 4; |
| private static final int URI_CANONICAL_ADDRESS = 5; |
| private static final int URI_PENDING_MSG = 6; |
| private static final int URI_COMPLETE_CONVERSATIONS = 7; |
| private static final int URI_UNDELIVERED_MSG = 8; |
| private static final int URI_CONVERSATIONS_SUBJECT = 9; |
| private static final int URI_NOTIFICATIONS = 10; |
| private static final int URI_OBSOLETE_THREADS = 11; |
| private static final int URI_DRAFT = 12; |
| private static final int URI_CANONICAL_ADDRESSES = 13; |
| private static final int URI_SEARCH = 14; |
| private static final int URI_SEARCH_SUGGEST = 15; |
| private static final int URI_FIRST_LOCKED_MESSAGE_ALL = 16; |
| private static final int URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID = 17; |
| private static final int URI_MESSAGE_ID_TO_THREAD = 18; |
| |
| /** |
| * the name of the table that is used to store the queue of |
| * messages(both MMS and SMS) to be sent/downloaded. |
| */ |
| public static final String TABLE_PENDING_MSG = "pending_msgs"; |
| |
| /** |
| * the name of the table that is used to store the canonical addresses for both SMS and MMS. |
| */ |
| private static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses"; |
| |
| // These constants are used to construct union queries across the |
| // MMS and SMS base tables. |
| |
| // These are the columns that appear in both the MMS ("pdu") and |
| // SMS ("sms") message tables. |
| private static final String[] MMS_SMS_COLUMNS = |
| { BaseColumns._ID, Mms.DATE, Mms.DATE_SENT, Mms.READ, Mms.THREAD_ID, Mms.LOCKED }; |
| |
| // These are the columns that appear only in the MMS message |
| // table. |
| private static final String[] MMS_ONLY_COLUMNS = { |
| Mms.CONTENT_CLASS, Mms.CONTENT_LOCATION, Mms.CONTENT_TYPE, |
| Mms.DELIVERY_REPORT, Mms.EXPIRY, Mms.MESSAGE_CLASS, Mms.MESSAGE_ID, |
| Mms.MESSAGE_SIZE, Mms.MESSAGE_TYPE, Mms.MESSAGE_BOX, Mms.PRIORITY, |
| Mms.READ_STATUS, Mms.RESPONSE_STATUS, Mms.RESPONSE_TEXT, |
| Mms.RETRIEVE_STATUS, Mms.RETRIEVE_TEXT_CHARSET, Mms.REPORT_ALLOWED, |
| Mms.READ_REPORT, Mms.STATUS, Mms.SUBJECT, Mms.SUBJECT_CHARSET, |
| Mms.TRANSACTION_ID, Mms.MMS_VERSION }; |
| |
| // These are the columns that appear only in the SMS message |
| // table. |
| private static final String[] SMS_ONLY_COLUMNS = |
| { "address", "body", "person", "reply_path_present", |
| "service_center", "status", "subject", "type", "error_code" }; |
| |
| // These are all the columns that appear in the "threads" table. |
| private static final String[] THREADS_COLUMNS = { |
| BaseColumns._ID, |
| ThreadsColumns.DATE, |
| ThreadsColumns.RECIPIENT_IDS, |
| ThreadsColumns.MESSAGE_COUNT |
| }; |
| |
| private static final String[] CANONICAL_ADDRESSES_COLUMNS_1 = |
| new String[] { CanonicalAddressesColumns.ADDRESS }; |
| |
| private static final String[] CANONICAL_ADDRESSES_COLUMNS_2 = |
| new String[] { CanonicalAddressesColumns._ID, |
| CanonicalAddressesColumns.ADDRESS }; |
| |
| // These are all the columns that appear in the MMS and SMS |
| // message tables. |
| private static final String[] UNION_COLUMNS = |
| new String[MMS_SMS_COLUMNS.length |
| + MMS_ONLY_COLUMNS.length |
| + SMS_ONLY_COLUMNS.length]; |
| |
| // These are all the columns that appear in the MMS table. |
| private static final Set<String> MMS_COLUMNS = new HashSet<String>(); |
| |
| // These are all the columns that appear in the SMS table. |
| private static final Set<String> SMS_COLUMNS = new HashSet<String>(); |
| |
| private static final String VND_ANDROID_DIR_MMS_SMS = |
| "vnd.android-dir/mms-sms"; |
| |
| private static final String[] ID_PROJECTION = { BaseColumns._ID }; |
| |
| private static final String[] EMPTY_STRING_ARRAY = new String[0]; |
| |
| private static final String SMS_CONVERSATION_CONSTRAINT = "(" + |
| Sms.TYPE + " != " + Sms.MESSAGE_TYPE_DRAFT + ")"; |
| |
| private static final String MMS_CONVERSATION_CONSTRAINT = "(" + |
| Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS + " AND (" + |
| Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_SEND_REQ + " OR " + |
| Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + " OR " + |
| Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + "))"; |
| |
| // Search on the words table but return the rows from the corresponding sms table |
| private static final String SMS_QUERY = |
| "SELECT sms._id AS _id,thread_id,address,body,date,date_sent,index_text,words._id " + |
| "FROM sms,words WHERE (index_text MATCH ? " + |
| "AND sms._id=words.source_id AND words.table_to_use=1)"; |
| |
| // Search on the words table but return the rows from the corresponding parts table |
| private static final String MMS_QUERY = |
| "SELECT pdu._id,thread_id,addr.address,part.text " + |
| "AS body,pdu.date,pdu.date_sent,index_text,words._id " + |
| "FROM pdu,part,addr,words WHERE ((part.mid=pdu._id) AND " + |
| "(addr.msg_id=pdu._id) AND " + |
| "(addr.type=" + PduHeaders.TO + ") AND " + |
| "(part.ct='text/plain') AND " + |
| "(index_text MATCH ?) AND " + |
| "(part._id = words.source_id) AND " + |
| "(words.table_to_use=2))"; |
| |
| // This code queries the sms and mms tables and returns a unified result set |
| // of text matches. We query the sms table which is pretty simple. We also |
| // query the pdu, part and addr table to get the mms result. Notet we're |
| // using a UNION so we have to have the same number of result columns from |
| // both queries. |
| private static final String SMS_MMS_QUERY = |
| SMS_QUERY + " UNION " + MMS_QUERY + |
| " GROUP BY thread_id ORDER BY thread_id ASC, date DESC"; |
| |
| private static final String AUTHORITY = "mms-sms"; |
| |
| static { |
| URI_MATCHER.addURI(AUTHORITY, "conversations", URI_CONVERSATIONS); |
| URI_MATCHER.addURI(AUTHORITY, "complete-conversations", URI_COMPLETE_CONVERSATIONS); |
| |
| // In these patterns, "#" is the thread ID. |
| URI_MATCHER.addURI( |
| AUTHORITY, "conversations/#", URI_CONVERSATIONS_MESSAGES); |
| URI_MATCHER.addURI( |
| AUTHORITY, "conversations/#/recipients", |
| URI_CONVERSATIONS_RECIPIENTS); |
| |
| URI_MATCHER.addURI( |
| AUTHORITY, "conversations/#/subject", |
| URI_CONVERSATIONS_SUBJECT); |
| |
| // URI for deleting obsolete threads. |
| URI_MATCHER.addURI(AUTHORITY, "conversations/obsolete", URI_OBSOLETE_THREADS); |
| |
| URI_MATCHER.addURI( |
| AUTHORITY, "messages/byphone/*", |
| URI_MESSAGES_BY_PHONE); |
| |
| // In this pattern, two query parameter names are expected: |
| // "subject" and "recipient." Multiple "recipient" parameters |
| // may be present. |
| URI_MATCHER.addURI(AUTHORITY, "threadID", URI_THREAD_ID); |
| |
| // Use this pattern to query the canonical address by given ID. |
| URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS); |
| |
| // Use this pattern to query all canonical addresses. |
| URI_MATCHER.addURI(AUTHORITY, "canonical-addresses", URI_CANONICAL_ADDRESSES); |
| |
| URI_MATCHER.addURI(AUTHORITY, "search", URI_SEARCH); |
| URI_MATCHER.addURI(AUTHORITY, "searchSuggest", URI_SEARCH_SUGGEST); |
| |
| // In this pattern, two query parameters may be supplied: |
| // "protocol" and "message." For example: |
| // content://mms-sms/pending? |
| // -> Return all pending messages; |
| // content://mms-sms/pending?protocol=sms |
| // -> Only return pending SMs; |
| // content://mms-sms/pending?protocol=mms&message=1 |
| // -> Return the the pending MM which ID equals '1'. |
| // |
| URI_MATCHER.addURI(AUTHORITY, "pending", URI_PENDING_MSG); |
| |
| // Use this pattern to get a list of undelivered messages. |
| URI_MATCHER.addURI(AUTHORITY, "undelivered", URI_UNDELIVERED_MSG); |
| |
| // Use this pattern to see what delivery status reports (for |
| // both MMS and SMS) have not been delivered to the user. |
| URI_MATCHER.addURI(AUTHORITY, "notifications", URI_NOTIFICATIONS); |
| |
| URI_MATCHER.addURI(AUTHORITY, "draft", URI_DRAFT); |
| |
| URI_MATCHER.addURI(AUTHORITY, "locked", URI_FIRST_LOCKED_MESSAGE_ALL); |
| |
| URI_MATCHER.addURI(AUTHORITY, "locked/#", URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID); |
| |
| URI_MATCHER.addURI(AUTHORITY, "messageIdToThread", URI_MESSAGE_ID_TO_THREAD); |
| initializeColumnSets(); |
| } |
| |
| private SQLiteOpenHelper mOpenHelper; |
| |
| private boolean mUseStrictPhoneNumberComparation; |
| |
| @Override |
| public boolean onCreate() { |
| mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext()); |
| mUseStrictPhoneNumberComparation = |
| getContext().getResources().getBoolean( |
| com.android.internal.R.bool.config_use_strict_phone_number_comparation); |
| return true; |
| } |
| |
| @Override |
| public Cursor query(Uri uri, String[] projection, |
| String selection, String[] selectionArgs, String sortOrder) { |
| SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| Cursor cursor = null; |
| switch(URI_MATCHER.match(uri)) { |
| case URI_COMPLETE_CONVERSATIONS: |
| cursor = getCompleteConversations(projection, selection, sortOrder); |
| break; |
| case URI_CONVERSATIONS: |
| String simple = uri.getQueryParameter("simple"); |
| if ((simple != null) && simple.equals("true")) { |
| String threadType = uri.getQueryParameter("thread_type"); |
| if (!TextUtils.isEmpty(threadType)) { |
| selection = concatSelections( |
| selection, Threads.TYPE + "=" + threadType); |
| } |
| cursor = getSimpleConversations( |
| projection, selection, selectionArgs, sortOrder); |
| } else { |
| cursor = getConversations( |
| projection, selection, sortOrder); |
| } |
| break; |
| case URI_CONVERSATIONS_MESSAGES: |
| cursor = getConversationMessages(uri.getPathSegments().get(1), projection, |
| selection, sortOrder); |
| break; |
| case URI_CONVERSATIONS_RECIPIENTS: |
| cursor = getConversationById( |
| uri.getPathSegments().get(1), projection, selection, |
| selectionArgs, sortOrder); |
| break; |
| case URI_CONVERSATIONS_SUBJECT: |
| cursor = getConversationById( |
| uri.getPathSegments().get(1), projection, selection, |
| selectionArgs, sortOrder); |
| break; |
| case URI_MESSAGES_BY_PHONE: |
| cursor = getMessagesByPhoneNumber( |
| uri.getPathSegments().get(2), projection, selection, sortOrder); |
| break; |
| case URI_THREAD_ID: |
| List<String> recipients = uri.getQueryParameters("recipient"); |
| |
| cursor = getThreadId(recipients); |
| break; |
| case URI_CANONICAL_ADDRESS: { |
| String extraSelection = "_id=" + uri.getPathSegments().get(1); |
| String finalSelection = TextUtils.isEmpty(selection) |
| ? extraSelection : extraSelection + " AND " + selection; |
| cursor = db.query(TABLE_CANONICAL_ADDRESSES, |
| CANONICAL_ADDRESSES_COLUMNS_1, |
| finalSelection, |
| selectionArgs, |
| null, null, |
| sortOrder); |
| break; |
| } |
| case URI_CANONICAL_ADDRESSES: |
| cursor = db.query(TABLE_CANONICAL_ADDRESSES, |
| CANONICAL_ADDRESSES_COLUMNS_2, |
| selection, |
| selectionArgs, |
| null, null, |
| sortOrder); |
| break; |
| case URI_SEARCH_SUGGEST: { |
| String searchString = uri.getQueryParameter("pattern"); |
| |
| // find the words which match the pattern using the snippet function. The |
| // snippet function parameters mainly describe how to format the result. |
| // See http://www.sqlite.org/fts3.html#section_4_2 for details. |
| String query = String.format("SELECT snippet(words, '', ' ', '', 1, 1) as snippet FROM words WHERE index_text MATCH '%s*' ORDER BY snippet LIMIT 50;", searchString); |
| if ( sortOrder != null |
| || selection != null |
| || selectionArgs != null |
| || projection != null) { |
| throw new IllegalArgumentException( |
| "do not specify sortOrder, selection, selectionArgs, or projection" + |
| "with this query"); |
| } |
| |
| cursor = db.rawQuery(query, null); |
| break; |
| } |
| case URI_MESSAGE_ID_TO_THREAD: { |
| // Given a message ID and an indicator for SMS vs. MMS return |
| // the thread id of the corresponding thread. |
| try { |
| long id = Long.parseLong(uri.getQueryParameter("row_id")); |
| switch (Integer.parseInt(uri.getQueryParameter("table_to_use"))) { |
| case 1: // sms |
| cursor = db.query( |
| "sms", |
| new String[] { "thread_id" }, |
| "_id=?", |
| new String[] { String.valueOf(id) }, |
| null, |
| null, |
| null); |
| break; |
| case 2: // mms |
| String mmsQuery = |
| "SELECT thread_id FROM pdu,part WHERE ((part.mid=pdu._id) AND " + |
| "(part._id=?))"; |
| cursor = db.rawQuery(mmsQuery, new String[] { String.valueOf(id) }); |
| break; |
| } |
| } catch (NumberFormatException ex) { |
| // ignore... return empty cursor |
| } |
| break; |
| } |
| case URI_SEARCH: { |
| if ( sortOrder != null |
| || selection != null |
| || selectionArgs != null |
| || projection != null) { |
| throw new IllegalArgumentException( |
| "do not specify sortOrder, selection, selectionArgs, or projection" + |
| "with this query"); |
| } |
| |
| String searchString = uri.getQueryParameter("pattern") + "*"; |
| |
| try { |
| cursor = db.rawQuery(SMS_MMS_QUERY, new String[] { searchString, searchString }); |
| } catch (Exception ex) { |
| Log.e(LOG_TAG, "got exception: " + ex.toString()); |
| } |
| break; |
| } |
| case URI_PENDING_MSG: { |
| String protoName = uri.getQueryParameter("protocol"); |
| String msgId = uri.getQueryParameter("message"); |
| int proto = TextUtils.isEmpty(protoName) ? -1 |
| : (protoName.equals("sms") ? MmsSms.SMS_PROTO : MmsSms.MMS_PROTO); |
| |
| String extraSelection = (proto != -1) ? |
| (PendingMessages.PROTO_TYPE + "=" + proto) : " 0=0 "; |
| if (!TextUtils.isEmpty(msgId)) { |
| extraSelection += " AND " + PendingMessages.MSG_ID + "=" + msgId; |
| } |
| |
| String finalSelection = TextUtils.isEmpty(selection) |
| ? extraSelection : ("(" + extraSelection + ") AND " + selection); |
| String finalOrder = TextUtils.isEmpty(sortOrder) |
| ? PendingMessages.DUE_TIME : sortOrder; |
| cursor = db.query(TABLE_PENDING_MSG, null, |
| finalSelection, selectionArgs, null, null, finalOrder); |
| break; |
| } |
| case URI_UNDELIVERED_MSG: { |
| cursor = getUndeliveredMessages(projection, selection, |
| selectionArgs, sortOrder); |
| break; |
| } |
| case URI_DRAFT: { |
| cursor = getDraftThread(projection, selection, sortOrder); |
| break; |
| } |
| case URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID: { |
| long threadId; |
| try { |
| threadId = Long.parseLong(uri.getLastPathSegment()); |
| } catch (NumberFormatException e) { |
| Log.e(LOG_TAG, "Thread ID must be a long."); |
| break; |
| } |
| cursor = getFirstLockedMessage(projection, "thread_id=" + Long.toString(threadId), |
| sortOrder); |
| break; |
| } |
| case URI_FIRST_LOCKED_MESSAGE_ALL: { |
| cursor = getFirstLockedMessage(projection, selection, sortOrder); |
| break; |
| } |
| default: |
| throw new IllegalStateException("Unrecognized URI:" + uri); |
| } |
| |
| cursor.setNotificationUri(getContext().getContentResolver(), MmsSms.CONTENT_URI); |
| return cursor; |
| } |
| |
| /** |
| * Return the canonical address ID for this address. |
| */ |
| private long getSingleAddressId(String address) { |
| boolean isEmail = Mms.isEmailAddress(address); |
| boolean isPhoneNumber = Mms.isPhoneNumber(address); |
| |
| // We lowercase all email addresses, but not addresses that aren't numbers, because |
| // that would incorrectly turn an address such as "My Vodafone" into "my vodafone" |
| // and the thread title would be incorrect when displayed in the UI. |
| String refinedAddress = isEmail ? address.toLowerCase() : address; |
| |
| String selection = "address=?"; |
| String[] selectionArgs; |
| long retVal = -1L; |
| |
| if (!isPhoneNumber) { |
| selectionArgs = new String[] { refinedAddress }; |
| } else { |
| selection += " OR PHONE_NUMBERS_EQUAL(address, ?, " + |
| (mUseStrictPhoneNumberComparation ? 1 : 0) + ")"; |
| selectionArgs = new String[] { refinedAddress, refinedAddress }; |
| } |
| |
| Cursor cursor = null; |
| |
| try { |
| SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| cursor = db.query( |
| "canonical_addresses", ID_PROJECTION, |
| selection, selectionArgs, null, null, null); |
| |
| if (cursor.getCount() == 0) { |
| ContentValues contentValues = new ContentValues(1); |
| contentValues.put(CanonicalAddressesColumns.ADDRESS, refinedAddress); |
| |
| db = mOpenHelper.getWritableDatabase(); |
| retVal = db.insert("canonical_addresses", |
| CanonicalAddressesColumns.ADDRESS, contentValues); |
| |
| Log.d(LOG_TAG, "getSingleAddressId: insert new canonical_address for " + |
| /*address*/ "xxxxxx" + ", _id=" + retVal); |
| |
| return retVal; |
| } |
| |
| if (cursor.moveToFirst()) { |
| retVal = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)); |
| } |
| } finally { |
| if (cursor != null) { |
| cursor.close(); |
| } |
| } |
| |
| return retVal; |
| } |
| |
| /** |
| * Return the canonical address IDs for these addresses. |
| */ |
| private Set<Long> getAddressIds(List<String> addresses) { |
| Set<Long> result = new HashSet<Long>(addresses.size()); |
| |
| for (String address : addresses) { |
| if (!address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) { |
| long id = getSingleAddressId(address); |
| if (id != -1L) { |
| result.add(id); |
| } else { |
| Log.e(LOG_TAG, "getAddressIds: address ID not found for " + address); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Return a sorted array of the given Set of Longs. |
| */ |
| private long[] getSortedSet(Set<Long> numbers) { |
| int size = numbers.size(); |
| long[] result = new long[size]; |
| int i = 0; |
| |
| for (Long number : numbers) { |
| result[i++] = number; |
| } |
| |
| if (size > 1) { |
| Arrays.sort(result); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Return a String of the numbers in the given array, in order, |
| * separated by spaces. |
| */ |
| private String getSpaceSeparatedNumbers(long[] numbers) { |
| int size = numbers.length; |
| StringBuilder buffer = new StringBuilder(); |
| |
| for (int i = 0; i < size; i++) { |
| if (i != 0) { |
| buffer.append(' '); |
| } |
| buffer.append(numbers[i]); |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Insert a record for a new thread. |
| */ |
| private void insertThread(String recipientIds, int numberOfRecipients) { |
| ContentValues values = new ContentValues(4); |
| |
| long date = System.currentTimeMillis(); |
| values.put(ThreadsColumns.DATE, date - date % 1000); |
| values.put(ThreadsColumns.RECIPIENT_IDS, recipientIds); |
| if (numberOfRecipients > 1) { |
| values.put(Threads.TYPE, Threads.BROADCAST_THREAD); |
| } |
| values.put(ThreadsColumns.MESSAGE_COUNT, 0); |
| |
| long result = mOpenHelper.getWritableDatabase().insert("threads", null, values); |
| Log.d(LOG_TAG, "insertThread: created new thread_id " + result + |
| " for recipientIds " + /*recipientIds*/ "xxxxxxx"); |
| |
| getContext().getContentResolver().notifyChange(MmsSms.CONTENT_URI, null); |
| } |
| |
| private static final String THREAD_QUERY = |
| "SELECT _id FROM threads " + "WHERE recipient_ids=?"; |
| |
| /** |
| * Return the thread ID for this list of |
| * recipients IDs. If no thread exists with this ID, create |
| * one and return it. Callers should always use |
| * Threads.getThreadId to access this information. |
| */ |
| private synchronized Cursor getThreadId(List<String> recipients) { |
| Set<Long> addressIds = getAddressIds(recipients); |
| String recipientIds = ""; |
| |
| // optimize for size==1, which should be most of the cases |
| if (addressIds.size() == 1) { |
| for (Long addressId : addressIds) { |
| recipientIds = Long.toString(addressId); |
| } |
| } else { |
| recipientIds = getSpaceSeparatedNumbers(getSortedSet(addressIds)); |
| } |
| |
| if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) { |
| Log.d(LOG_TAG, "getThreadId: recipientIds (selectionArgs) =" + |
| /*recipientIds*/ "xxxxxxx"); |
| } |
| |
| String[] selectionArgs = new String[] { recipientIds }; |
| SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| Cursor cursor = db.rawQuery(THREAD_QUERY, selectionArgs); |
| |
| if (cursor.getCount() == 0) { |
| cursor.close(); |
| |
| Log.d(LOG_TAG, "getThreadId: create new thread_id for recipients " + |
| /*recipients*/ "xxxxxxxx"); |
| insertThread(recipientIds, recipients.size()); |
| |
| db = mOpenHelper.getReadableDatabase(); // In case insertThread closed it |
| cursor = db.rawQuery(THREAD_QUERY, selectionArgs); |
| } |
| |
| if (cursor.getCount() > 1) { |
| Log.w(LOG_TAG, "getThreadId: why is cursorCount=" + cursor.getCount()); |
| } |
| |
| return cursor; |
| } |
| |
| private static String concatSelections(String selection1, String selection2) { |
| if (TextUtils.isEmpty(selection1)) { |
| return selection2; |
| } else if (TextUtils.isEmpty(selection2)) { |
| return selection1; |
| } else { |
| return selection1 + " AND " + selection2; |
| } |
| } |
| |
| /** |
| * If a null projection is given, return the union of all columns |
| * in both the MMS and SMS messages tables. Otherwise, return the |
| * given projection. |
| */ |
| private static String[] handleNullMessageProjection( |
| String[] projection) { |
| return projection == null ? UNION_COLUMNS : projection; |
| } |
| |
| /** |
| * If a null projection is given, return the set of all columns in |
| * the threads table. Otherwise, return the given projection. |
| */ |
| private static String[] handleNullThreadsProjection( |
| String[] projection) { |
| return projection == null ? THREADS_COLUMNS : projection; |
| } |
| |
| /** |
| * If a null sort order is given, return "normalized_date ASC". |
| * Otherwise, return the given sort order. |
| */ |
| private static String handleNullSortOrder (String sortOrder) { |
| return sortOrder == null ? "normalized_date ASC" : sortOrder; |
| } |
| |
| /** |
| * Return existing threads in the database. |
| */ |
| private Cursor getSimpleConversations(String[] projection, String selection, |
| String[] selectionArgs, String sortOrder) { |
| return mOpenHelper.getReadableDatabase().query("threads", projection, |
| selection, selectionArgs, null, null, " date DESC"); |
| } |
| |
| /** |
| * Return the thread which has draft in both MMS and SMS. |
| * |
| * Use this query: |
| * |
| * SELECT ... |
| * FROM (SELECT _id, thread_id, ... |
| * FROM pdu |
| * WHERE msg_box = 3 AND ... |
| * UNION |
| * SELECT _id, thread_id, ... |
| * FROM sms |
| * WHERE type = 3 AND ... |
| * ) |
| * ; |
| */ |
| private Cursor getDraftThread(String[] projection, String selection, |
| String sortOrder) { |
| String[] innerProjection = new String[] {BaseColumns._ID, Conversations.THREAD_ID}; |
| SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); |
| SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); |
| |
| mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU); |
| smsQueryBuilder.setTables(SmsProvider.TABLE_SMS); |
| |
| String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection, |
| MMS_COLUMNS, 1, "mms", |
| concatSelections(selection, Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_DRAFTS), |
| null, null); |
| String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection, |
| SMS_COLUMNS, 1, "sms", |
| concatSelections(selection, Sms.TYPE + "=" + Sms.MESSAGE_TYPE_DRAFT), |
| null, null); |
| SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); |
| |
| unionQueryBuilder.setDistinct(true); |
| |
| String unionQuery = unionQueryBuilder.buildUnionQuery( |
| new String[] { mmsSubQuery, smsSubQuery }, null, null); |
| |
| SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); |
| |
| outerQueryBuilder.setTables("(" + unionQuery + ")"); |
| |
| String outerQuery = outerQueryBuilder.buildQuery( |
| projection, null, null, null, sortOrder, null); |
| |
| return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY); |
| } |
| |
| /** |
| * Return the most recent message in each conversation in both MMS |
| * and SMS. |
| * |
| * Use this query: |
| * |
| * SELECT ... |
| * FROM (SELECT thread_id AS tid, date * 1000 AS normalized_date, ... |
| * FROM pdu |
| * WHERE msg_box != 3 AND ... |
| * GROUP BY thread_id |
| * HAVING date = MAX(date) |
| * UNION |
| * SELECT thread_id AS tid, date AS normalized_date, ... |
| * FROM sms |
| * WHERE ... |
| * GROUP BY thread_id |
| * HAVING date = MAX(date)) |
| * GROUP BY tid |
| * HAVING normalized_date = MAX(normalized_date); |
| * |
| * The msg_box != 3 comparisons ensure that we don't include draft |
| * messages. |
| */ |
| private Cursor getConversations(String[] projection, String selection, |
| String sortOrder) { |
| SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); |
| SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); |
| |
| mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU); |
| smsQueryBuilder.setTables(SmsProvider.TABLE_SMS); |
| |
| String[] columns = handleNullMessageProjection(projection); |
| String[] innerMmsProjection = makeProjectionWithDateAndThreadId( |
| UNION_COLUMNS, 1000); |
| String[] innerSmsProjection = makeProjectionWithDateAndThreadId( |
| UNION_COLUMNS, 1); |
| String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection, |
| MMS_COLUMNS, 1, "mms", |
| concatSelections(selection, MMS_CONVERSATION_CONSTRAINT), |
| "thread_id", "date = MAX(date)"); |
| String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, |
| SMS_COLUMNS, 1, "sms", |
| concatSelections(selection, SMS_CONVERSATION_CONSTRAINT), |
| "thread_id", "date = MAX(date)"); |
| SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); |
| |
| unionQueryBuilder.setDistinct(true); |
| |
| String unionQuery = unionQueryBuilder.buildUnionQuery( |
| new String[] { mmsSubQuery, smsSubQuery }, null, null); |
| |
| SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); |
| |
| outerQueryBuilder.setTables("(" + unionQuery + ")"); |
| |
| String outerQuery = outerQueryBuilder.buildQuery( |
| columns, null, "tid", |
| "normalized_date = MAX(normalized_date)", sortOrder, null); |
| |
| return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY); |
| } |
| |
| /** |
| * Return the first locked message found in the union of MMS |
| * and SMS messages. |
| * |
| * Use this query: |
| * |
| * SELECT _id FROM pdu GROUP BY _id HAVING locked=1 UNION SELECT _id FROM sms GROUP |
| * BY _id HAVING locked=1 LIMIT 1 |
| * |
| * We limit by 1 because we're only interested in knowing if |
| * there is *any* locked message, not the actual messages themselves. |
| */ |
| private Cursor getFirstLockedMessage(String[] projection, String selection, |
| String sortOrder) { |
| SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); |
| SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); |
| |
| mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU); |
| smsQueryBuilder.setTables(SmsProvider.TABLE_SMS); |
| |
| String[] idColumn = new String[] { BaseColumns._ID }; |
| |
| // NOTE: buildUnionSubQuery *ignores* selectionArgs |
| String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, idColumn, |
| null, 1, "mms", |
| selection, |
| BaseColumns._ID, "locked=1"); |
| |
| String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, idColumn, |
| null, 1, "sms", |
| selection, |
| BaseColumns._ID, "locked=1"); |
| |
| SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); |
| |
| unionQueryBuilder.setDistinct(true); |
| |
| String unionQuery = unionQueryBuilder.buildUnionQuery( |
| new String[] { mmsSubQuery, smsSubQuery }, null, "1"); |
| |
| Cursor cursor = mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY); |
| |
| if (DEBUG) { |
| Log.v("MmsSmsProvider", "getFirstLockedMessage query: " + unionQuery); |
| Log.v("MmsSmsProvider", "cursor count: " + cursor.getCount()); |
| } |
| return cursor; |
| } |
| |
| /** |
| * Return every message in each conversation in both MMS |
| * and SMS. |
| */ |
| private Cursor getCompleteConversations(String[] projection, |
| String selection, String sortOrder) { |
| String unionQuery = buildConversationQuery(projection, selection, sortOrder); |
| |
| return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY); |
| } |
| |
| /** |
| * Add normalized date and thread_id to the list of columns for an |
| * inner projection. This is necessary so that the outer query |
| * can have access to these columns even if the caller hasn't |
| * requested them in the result. |
| */ |
| private String[] makeProjectionWithDateAndThreadId( |
| String[] projection, int dateMultiple) { |
| int projectionSize = projection.length; |
| String[] result = new String[projectionSize + 2]; |
| |
| result[0] = "thread_id AS tid"; |
| result[1] = "date * " + dateMultiple + " AS normalized_date"; |
| for (int i = 0; i < projectionSize; i++) { |
| result[i + 2] = projection[i]; |
| } |
| return result; |
| } |
| |
| /** |
| * Return the union of MMS and SMS messages for this thread ID. |
| */ |
| private Cursor getConversationMessages( |
| String threadIdString, String[] projection, String selection, |
| String sortOrder) { |
| try { |
| Long.parseLong(threadIdString); |
| } catch (NumberFormatException exception) { |
| Log.e(LOG_TAG, "Thread ID must be a Long."); |
| return null; |
| } |
| |
| String finalSelection = concatSelections( |
| selection, "thread_id = " + threadIdString); |
| String unionQuery = buildConversationQuery(projection, finalSelection, sortOrder); |
| |
| return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY); |
| } |
| |
| /** |
| * Return the union of MMS and SMS messages whose recipients |
| * included this phone number. |
| * |
| * Use this query: |
| * |
| * SELECT ... |
| * FROM pdu, (SELECT _id AS address_id |
| * FROM addr |
| * WHERE (address='<phoneNumber>' OR |
| * PHONE_NUMBERS_EQUAL(addr.address, '<phoneNumber>', 1/0))) |
| * AS matching_addresses |
| * WHERE pdu._id = matching_addresses.address_id |
| * UNION |
| * SELECT ... |
| * FROM sms |
| * WHERE (address='<phoneNumber>' OR PHONE_NUMBERS_EQUAL(sms.address, '<phoneNumber>', 1/0)); |
| */ |
| private Cursor getMessagesByPhoneNumber( |
| String phoneNumber, String[] projection, String selection, |
| String sortOrder) { |
| String escapedPhoneNumber = DatabaseUtils.sqlEscapeString(phoneNumber); |
| String finalMmsSelection = |
| concatSelections( |
| selection, |
| "pdu._id = matching_addresses.address_id"); |
| String finalSmsSelection = |
| concatSelections( |
| selection, |
| "(address=" + escapedPhoneNumber + " OR PHONE_NUMBERS_EQUAL(address, " + |
| escapedPhoneNumber + |
| (mUseStrictPhoneNumberComparation ? ", 1))" : ", 0))")); |
| SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); |
| SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); |
| |
| mmsQueryBuilder.setDistinct(true); |
| smsQueryBuilder.setDistinct(true); |
| mmsQueryBuilder.setTables( |
| MmsProvider.TABLE_PDU + |
| ", (SELECT _id AS address_id " + |
| "FROM addr WHERE (address=" + escapedPhoneNumber + |
| " OR PHONE_NUMBERS_EQUAL(addr.address, " + |
| escapedPhoneNumber + |
| (mUseStrictPhoneNumberComparation ? ", 1))) " : ", 0))) ") + |
| "AS matching_addresses"); |
| smsQueryBuilder.setTables(SmsProvider.TABLE_SMS); |
| |
| String[] columns = handleNullMessageProjection(projection); |
| String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, MMS_COLUMNS, |
| 0, "mms", finalMmsSelection, null, null); |
| String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, SMS_COLUMNS, |
| 0, "sms", finalSmsSelection, null, null); |
| SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); |
| |
| unionQueryBuilder.setDistinct(true); |
| |
| String unionQuery = unionQueryBuilder.buildUnionQuery( |
| new String[] { mmsSubQuery, smsSubQuery }, sortOrder, null); |
| |
| return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY); |
| } |
| |
| /** |
| * Return the conversation of certain thread ID. |
| */ |
| private Cursor getConversationById( |
| String threadIdString, String[] projection, String selection, |
| String[] selectionArgs, String sortOrder) { |
| try { |
| Long.parseLong(threadIdString); |
| } catch (NumberFormatException exception) { |
| Log.e(LOG_TAG, "Thread ID must be a Long."); |
| return null; |
| } |
| |
| String extraSelection = "_id=" + threadIdString; |
| String finalSelection = concatSelections(selection, extraSelection); |
| SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); |
| String[] columns = handleNullThreadsProjection(projection); |
| |
| queryBuilder.setDistinct(true); |
| queryBuilder.setTables("threads"); |
| return queryBuilder.query( |
| mOpenHelper.getReadableDatabase(), columns, finalSelection, |
| selectionArgs, sortOrder, null, null); |
| } |
| |
| private static String joinPduAndPendingMsgTables() { |
| return MmsProvider.TABLE_PDU + " LEFT JOIN " + TABLE_PENDING_MSG |
| + " ON pdu._id = pending_msgs.msg_id"; |
| } |
| |
| private static String[] createMmsProjection(String[] old) { |
| String[] newProjection = new String[old.length]; |
| for (int i = 0; i < old.length; i++) { |
| if (old[i].equals(BaseColumns._ID)) { |
| newProjection[i] = "pdu._id"; |
| } else { |
| newProjection[i] = old[i]; |
| } |
| } |
| return newProjection; |
| } |
| |
| private Cursor getUndeliveredMessages( |
| String[] projection, String selection, String[] selectionArgs, |
| String sortOrder) { |
| String[] mmsProjection = createMmsProjection(projection); |
| |
| SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); |
| SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); |
| |
| mmsQueryBuilder.setTables(joinPduAndPendingMsgTables()); |
| smsQueryBuilder.setTables(SmsProvider.TABLE_SMS); |
| |
| String finalMmsSelection = concatSelections( |
| selection, Mms.MESSAGE_BOX + " = " + Mms.MESSAGE_BOX_OUTBOX); |
| String finalSmsSelection = concatSelections( |
| selection, "(" + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_OUTBOX |
| + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_FAILED |
| + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_QUEUED + ")"); |
| |
| String[] smsColumns = handleNullMessageProjection(projection); |
| String[] mmsColumns = handleNullMessageProjection(mmsProjection); |
| String[] innerMmsProjection = makeProjectionWithDateAndThreadId( |
| mmsColumns, 1000); |
| String[] innerSmsProjection = makeProjectionWithDateAndThreadId( |
| smsColumns, 1); |
| |
| Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS); |
| columnsPresentInTable.add("pdu._id"); |
| columnsPresentInTable.add(PendingMessages.ERROR_TYPE); |
| String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection, |
| columnsPresentInTable, 1, "mms", finalMmsSelection, |
| null, null); |
| String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, |
| SMS_COLUMNS, 1, "sms", finalSmsSelection, |
| null, null); |
| SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); |
| |
| unionQueryBuilder.setDistinct(true); |
| |
| String unionQuery = unionQueryBuilder.buildUnionQuery( |
| new String[] { smsSubQuery, mmsSubQuery }, null, null); |
| |
| SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); |
| |
| outerQueryBuilder.setTables("(" + unionQuery + ")"); |
| |
| String outerQuery = outerQueryBuilder.buildQuery( |
| smsColumns, null, null, null, sortOrder, null); |
| |
| return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY); |
| } |
| |
| /** |
| * Add normalized date to the list of columns for an inner |
| * projection. |
| */ |
| private static String[] makeProjectionWithNormalizedDate( |
| String[] projection, int dateMultiple) { |
| int projectionSize = projection.length; |
| String[] result = new String[projectionSize + 1]; |
| |
| result[0] = "date * " + dateMultiple + " AS normalized_date"; |
| System.arraycopy(projection, 0, result, 1, projectionSize); |
| return result; |
| } |
| |
| private static String buildConversationQuery(String[] projection, |
| String selection, String sortOrder) { |
| String[] mmsProjection = createMmsProjection(projection); |
| |
| SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); |
| SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); |
| |
| mmsQueryBuilder.setDistinct(true); |
| smsQueryBuilder.setDistinct(true); |
| mmsQueryBuilder.setTables(joinPduAndPendingMsgTables()); |
| smsQueryBuilder.setTables(SmsProvider.TABLE_SMS); |
| |
| String[] smsColumns = handleNullMessageProjection(projection); |
| String[] mmsColumns = handleNullMessageProjection(mmsProjection); |
| String[] innerMmsProjection = makeProjectionWithNormalizedDate(mmsColumns, 1000); |
| String[] innerSmsProjection = makeProjectionWithNormalizedDate(smsColumns, 1); |
| |
| Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS); |
| columnsPresentInTable.add("pdu._id"); |
| columnsPresentInTable.add(PendingMessages.ERROR_TYPE); |
| |
| String mmsSelection = concatSelections(selection, |
| Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS); |
| String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection, |
| columnsPresentInTable, 0, "mms", |
| concatSelections(mmsSelection, MMS_CONVERSATION_CONSTRAINT), |
| null, null); |
| String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( |
| MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, SMS_COLUMNS, |
| 0, "sms", concatSelections(selection, SMS_CONVERSATION_CONSTRAINT), |
| null, null); |
| SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); |
| |
| unionQueryBuilder.setDistinct(true); |
| |
| String unionQuery = unionQueryBuilder.buildUnionQuery( |
| new String[] { smsSubQuery, mmsSubQuery }, |
| handleNullSortOrder(sortOrder), null); |
| |
| SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); |
| |
| outerQueryBuilder.setTables("(" + unionQuery + ")"); |
| |
| return outerQueryBuilder.buildQuery( |
| smsColumns, null, null, null, sortOrder, null); |
| } |
| |
| @Override |
| public String getType(Uri uri) { |
| return VND_ANDROID_DIR_MMS_SMS; |
| } |
| |
| @Override |
| public int delete(Uri uri, String selection, |
| String[] selectionArgs) { |
| SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| Context context = getContext(); |
| int affectedRows = 0; |
| |
| switch(URI_MATCHER.match(uri)) { |
| case URI_CONVERSATIONS_MESSAGES: |
| long threadId; |
| try { |
| threadId = Long.parseLong(uri.getLastPathSegment()); |
| } catch (NumberFormatException e) { |
| Log.e(LOG_TAG, "Thread ID must be a long."); |
| break; |
| } |
| affectedRows = deleteConversation(uri, selection, selectionArgs); |
| MmsSmsDatabaseHelper.updateThread(db, threadId); |
| break; |
| case URI_CONVERSATIONS: |
| affectedRows = MmsProvider.deleteMessages(context, db, |
| selection, selectionArgs, uri) |
| + db.delete("sms", selection, selectionArgs); |
| // Intentionally don't pass the selection variable to updateAllThreads. |
| // When we pass in "locked=0" there, the thread will get excluded from |
| // the selection and not get updated. |
| MmsSmsDatabaseHelper.updateAllThreads(db, null, null); |
| break; |
| case URI_OBSOLETE_THREADS: |
| affectedRows = db.delete("threads", |
| "_id NOT IN (SELECT DISTINCT thread_id FROM sms " + |
| "UNION SELECT DISTINCT thread_id FROM pdu)", null); |
| break; |
| default: |
| throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES); |
| } |
| |
| if (affectedRows > 0) { |
| context.getContentResolver().notifyChange(MmsSms.CONTENT_URI, null); |
| } |
| return affectedRows; |
| } |
| |
| /** |
| * Delete the conversation with the given thread ID. |
| */ |
| private int deleteConversation(Uri uri, String selection, String[] selectionArgs) { |
| String threadId = uri.getLastPathSegment(); |
| |
| SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| String finalSelection = concatSelections(selection, "thread_id = " + threadId); |
| return MmsProvider.deleteMessages(getContext(), db, finalSelection, |
| selectionArgs, uri) |
| + db.delete("sms", finalSelection, selectionArgs); |
| } |
| |
| @Override |
| public Uri insert(Uri uri, ContentValues values) { |
| throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES); |
| } |
| |
| @Override |
| public int update(Uri uri, ContentValues values, |
| String selection, String[] selectionArgs) { |
| SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| int affectedRows = 0; |
| switch(URI_MATCHER.match(uri)) { |
| case URI_CONVERSATIONS_MESSAGES: |
| String threadIdString = uri.getPathSegments().get(1); |
| affectedRows = updateConversation(threadIdString, values, |
| selection, selectionArgs); |
| break; |
| |
| case URI_PENDING_MSG: |
| affectedRows = db.update(TABLE_PENDING_MSG, values, selection, null); |
| break; |
| |
| case URI_CANONICAL_ADDRESS: { |
| String extraSelection = "_id=" + uri.getPathSegments().get(1); |
| String finalSelection = TextUtils.isEmpty(selection) |
| ? extraSelection : extraSelection + " AND " + selection; |
| |
| affectedRows = db.update(TABLE_CANONICAL_ADDRESSES, values, finalSelection, null); |
| break; |
| } |
| |
| default: |
| throw new UnsupportedOperationException( |
| NO_DELETES_INSERTS_OR_UPDATES); |
| } |
| |
| if (affectedRows > 0) { |
| getContext().getContentResolver().notifyChange( |
| MmsSms.CONTENT_URI, null); |
| } |
| return affectedRows; |
| } |
| |
| private int updateConversation( |
| String threadIdString, ContentValues values, String selection, |
| String[] selectionArgs) { |
| try { |
| Long.parseLong(threadIdString); |
| } catch (NumberFormatException exception) { |
| Log.e(LOG_TAG, "Thread ID must be a Long."); |
| return 0; |
| } |
| |
| SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| String finalSelection = concatSelections(selection, "thread_id=" + threadIdString); |
| return db.update(MmsProvider.TABLE_PDU, values, finalSelection, selectionArgs) |
| + db.update("sms", values, finalSelection, selectionArgs); |
| } |
| |
| /** |
| * Construct Sets of Strings containing exactly the columns |
| * present in each table. We will use this when constructing |
| * UNION queries across the MMS and SMS tables. |
| */ |
| private static void initializeColumnSets() { |
| int commonColumnCount = MMS_SMS_COLUMNS.length; |
| int mmsOnlyColumnCount = MMS_ONLY_COLUMNS.length; |
| int smsOnlyColumnCount = SMS_ONLY_COLUMNS.length; |
| Set<String> unionColumns = new HashSet<String>(); |
| |
| for (int i = 0; i < commonColumnCount; i++) { |
| MMS_COLUMNS.add(MMS_SMS_COLUMNS[i]); |
| SMS_COLUMNS.add(MMS_SMS_COLUMNS[i]); |
| unionColumns.add(MMS_SMS_COLUMNS[i]); |
| } |
| for (int i = 0; i < mmsOnlyColumnCount; i++) { |
| MMS_COLUMNS.add(MMS_ONLY_COLUMNS[i]); |
| unionColumns.add(MMS_ONLY_COLUMNS[i]); |
| } |
| for (int i = 0; i < smsOnlyColumnCount; i++) { |
| SMS_COLUMNS.add(SMS_ONLY_COLUMNS[i]); |
| unionColumns.add(SMS_ONLY_COLUMNS[i]); |
| } |
| |
| int i = 0; |
| for (String columnName : unionColumns) { |
| UNION_COLUMNS[i++] = columnName; |
| } |
| } |
| } |