FileDocCategorySizeDatePackage
MmsSmsProvider.javaAPI DocAndroid 1.5 API41646Wed May 06 22:42:48 BST 2009com.android.providers.telephony

MmsSmsProvider

public class MmsSmsProvider extends android.content.ContentProvider
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.

Fields Summary
private static final android.content.UriMatcher
URI_MATCHER
private static final String
LOG_TAG
private static final String
NO_DELETES_INSERTS_OR_UPDATES
private static final int
URI_CONVERSATIONS
private static final int
URI_CONVERSATIONS_MESSAGES
private static final int
URI_CONVERSATIONS_RECIPIENTS
private static final int
URI_MESSAGES_BY_PHONE
private static final int
URI_THREAD_ID
private static final int
URI_CANONICAL_ADDRESS
private static final int
URI_PENDING_MSG
private static final int
URI_COMPLETE_CONVERSATIONS
private static final int
URI_UNDELIVERED_MSG
private static final int
URI_CONVERSATIONS_SUBJECT
private static final int
URI_NOTIFICATIONS
private static final int
URI_OBSOLETE_THREADS
private static final int
URI_DRAFT
public static final String
TABLE_PENDING_MSG
the name of the table that is used to store the queue of messages(both MMS and SMS) to be sent/downloaded.
private static final String[]
MMS_SMS_COLUMNS
private static final String[]
MMS_ONLY_COLUMNS
private static final String[]
SMS_ONLY_COLUMNS
private static final String[]
THREADS_COLUMNS
private static final String[]
UNION_COLUMNS
private static final Set
MMS_COLUMNS
private static final Set
SMS_COLUMNS
private static final String
VND_ANDROID_DIR_MMS_SMS
private static final String[]
ID_PROJECTION
private static final String[]
EMPTY_STRING_ARRAY
private static final String
SMS_CONVERSATION_CONSTRAINT
private static final String
MMS_CONVERSATION_CONSTRAINT
private static final String
AUTHORITY
private android.database.sqlite.SQLiteOpenHelper
mOpenHelper
Constructors Summary
Methods Summary
private static java.lang.StringbuildConversationQuery(java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.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),
                selectionArgs, null, null);
        String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
                MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, SMS_COLUMNS,
                0, "sms", concatSelections(selection, SMS_CONVERSATION_CONSTRAINT),
                selectionArgs, 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, null, sortOrder, null);
    
private static java.lang.StringconcatSelections(java.lang.String selection1, java.lang.String selection2)

        if (TextUtils.isEmpty(selection1)) {
            return selection2;
        } else if (TextUtils.isEmpty(selection2)) {
            return selection1;
        } else {
            return selection1 + " AND " + selection2;
        }
    
private static java.lang.String[]createMmsProjection(java.lang.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;
    
public intdelete(android.net.Uri uri, java.lang.String selection, java.lang.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);
                MmsSmsDatabaseHelper.updateAllThreads(db, selection, selectionArgs);
                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;
    
private intdeleteConversation(android.net.Uri uri, java.lang.String selection, java.lang.String[] selectionArgs)
Delete the conversation with the given thread ID.

        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);
    
private java.util.SetgetAddressIds(java.util.List addresses)
Return the canonical address IDs for these 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, "Address ID not found for: " + address);
                }
            }
        }
        return result;
    
private android.database.CursorgetCompleteConversations(java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
Return every message in each conversation in both MMS and SMS.

        String unionQuery = buildConversationQuery(
                projection, selection, selectionArgs, sortOrder);

        return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
    
private android.database.CursorgetConversationById(java.lang.String threadIdString, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
Return the conversation of certain thread ID.

        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 android.database.CursorgetConversationMessages(java.lang.String threadIdString, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
Return the union of MMS and SMS messages for this thread ID.

        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, selectionArgs, sortOrder);

        return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
    
private android.database.CursorgetConversations(java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
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.

        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), selectionArgs,
                "thread_id", "date = MAX(date)");
        String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
                MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection,
                SMS_COLUMNS, 1, "sms",
                concatSelections(selection, SMS_CONVERSATION_CONSTRAINT), selectionArgs,
                "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, null, "tid",
                "normalized_date = MAX(normalized_date)", sortOrder, null);

        return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
    
private android.database.CursorgetDraftThread(java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
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 ... ) ;

        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),
                selectionArgs, null, null);
        String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
                MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection,
                SMS_COLUMNS, 1, "sms",
                concatSelections(selection, Sms.TYPE + "=" + Sms.MESSAGE_TYPE_DRAFT),
                selectionArgs, 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, null, sortOrder, null);

        return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
    
private android.database.CursorgetMessagesByPhoneNumber(java.lang.String phoneNumber, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
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 PHONE_NUMBERS_EQUAL(addr.address, '')) AS matching_addresses WHERE pdu._id = matching_addresses.address_id UNION SELECT ... FROM sms WHERE PHONE_NUMBERS_EQUAL(sms.address, '');

        String escapedPhoneNumber = DatabaseUtils.sqlEscapeString(phoneNumber);
        String finalMmsSelection =
                concatSelections(
                        selection,
                        "pdu._id = matching_addresses.address_id");
        String finalSmsSelection =
                concatSelections(
                        selection,
                        "PHONE_NUMBERS_EQUAL(address, " +
                        escapedPhoneNumber + ")");
        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 PHONE_NUMBERS_EQUAL(addr.address, " +
                escapedPhoneNumber + ")) " +
                "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, selectionArgs, null, null);
        String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
                MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, SMS_COLUMNS,
                0, "sms", finalSmsSelection, selectionArgs, 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);
    
private android.database.CursorgetSimpleConversations(java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
Return existing threads in the database.

        return mOpenHelper.getReadableDatabase().query("threads", projection,
                selection, selectionArgs, null, null, " date DESC");
    
private longgetSingleAddressId(java.lang.String address)
Return the canonical address ID for this address.

        boolean isEmail = Mms.isEmailAddress(address);
        String refinedAddress = isEmail ? address.toLowerCase() : address;
        String selection =
                isEmail
                ? "address = ?"
                : "PHONE_NUMBERS_EQUAL(address, ?)";
        String[] selectionArgs = new String[] { 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();
                return db.insert("canonical_addresses",
                        CanonicalAddressesColumns.ADDRESS, contentValues);
            }

            if (cursor.moveToFirst()) {
                return cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }

        return -1L;
    
private long[]getSortedSet(java.util.Set numbers)
Return a sorted array of the given Set of Longs.

        int size = numbers.size();
        long[] result = new long[size];
        int i = 0;

        for (Long number : numbers) {
            result[i++] = number;
        }
        Arrays.sort(result);
        return result;
    
private java.lang.StringgetSpaceSeparatedNumbers(long[] numbers)
Return a String of the numbers in the given array, in order, separated by spaces.

        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();
    
private synchronized android.database.CursorgetThreadId(java.util.List recipients)
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.

        String recipientIds =
                getSpaceSeparatedNumbers(
                        getSortedSet(getAddressIds(recipients)));
        String THREAD_QUERY = "SELECT _id FROM threads " +
                "WHERE recipient_ids = ?";

        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery(THREAD_QUERY, new String[] { recipientIds });

        if (cursor.getCount() == 0) {
            cursor.close();
            insertThread(recipientIds, recipients.size());
            db = mOpenHelper.getReadableDatabase();  // In case insertThread closed it
            cursor = db.rawQuery(THREAD_QUERY, new String[] { recipientIds });
        }

        return cursor;
    
public java.lang.StringgetType(android.net.Uri uri)

        return VND_ANDROID_DIR_MMS_SMS;
    
private android.database.CursorgetUndeliveredMessages(java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.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, selectionArgs,
                null, null);
        String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
                MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection,
                SMS_COLUMNS, 1, "sms", finalSmsSelection, selectionArgs,
                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, null, sortOrder, null);

        return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
    
private static java.lang.String[]handleNullMessageProjection(java.lang.String[] projection)
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.

        return projection == null ? UNION_COLUMNS : projection;
    
private static java.lang.StringhandleNullSortOrder(java.lang.String sortOrder)
If a null sort order is given, return "normalized_date ASC". Otherwise, return the given sort order.

        return sortOrder == null ? "normalized_date ASC" : sortOrder;
    
private static java.lang.String[]handleNullThreadsProjection(java.lang.String[] projection)
If a null projection is given, return the set of all columns in the threads table. Otherwise, return the given projection.

        return projection == null ? THREADS_COLUMNS : projection;
    
private static voidinitializeColumnSets()
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.

        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;
        }
    
public android.net.Uriinsert(android.net.Uri uri, android.content.ContentValues values)

        throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES);
    
private voidinsertThread(java.lang.String recipientIds, int numberOfRecipients)
Insert a record for a new thread.

        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);

        mOpenHelper.getWritableDatabase().insert("threads", null, values);
        getContext().getContentResolver().notifyChange(MmsSms.CONTENT_URI, null);
    
private static java.lang.StringjoinPduAndPendingMsgTables()

        return MmsProvider.TABLE_PDU + " LEFT JOIN " + TABLE_PENDING_MSG
                + " ON pdu._id = pending_msgs.msg_id";
    
private java.lang.String[]makeProjectionWithDateAndThreadId(java.lang.String[] projection, int dateMultiple)
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.

        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;
    
private static java.lang.String[]makeProjectionWithNormalizedDate(java.lang.String[] projection, int dateMultiple)
Add normalized date to the list of columns for an inner projection.

        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;
    
public booleanonCreate()

        mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
        return true;
    
public android.database.Cursorquery(android.net.Uri uri, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)

        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        Cursor cursor = null;

        switch(URI_MATCHER.match(uri)) {
            case URI_COMPLETE_CONVERSATIONS:
                cursor = getCompleteConversations(
                        projection, selection, selectionArgs, 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, selectionArgs, sortOrder);
                }
                break;
            case URI_CONVERSATIONS_MESSAGES:
                cursor = getConversationMessages(
                        uri.getPathSegments().get(1), projection, selection,
                        selectionArgs, 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,
                        selectionArgs, 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("canonical_addresses",
                        new String[] {"address"}, finalSelection, selectionArgs,
                        null, null, sortOrder);
                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, selectionArgs, sortOrder);
                break;
            }
            default:
                throw new IllegalStateException("Unrecognized URI:" + uri);
        }

        cursor.setNotificationUri(getContext().getContentResolver(), MmsSms.CONTENT_URI);
        return cursor;
    
public intupdate(android.net.Uri uri, android.content.ContentValues values, java.lang.String selection, java.lang.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;
            default:
                throw new UnsupportedOperationException(
                        NO_DELETES_INSERTS_OR_UPDATES);
        }

        if (affectedRows > 0) {
            getContext().getContentResolver().notifyChange(
                    MmsSms.CONTENT_URI, null);
        }
        return affectedRows;
    
private intupdateConversation(java.lang.String threadIdString, android.content.ContentValues values, java.lang.String selection, java.lang.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);