1/*
2 * Copyright (C) 2006 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
19import android.content.ContentProvider;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.UriMatcher;
23
24import android.database.Cursor;
25import android.database.DatabaseUtils;
26import android.database.sqlite.SQLiteDatabase;
27import android.database.sqlite.SQLiteOpenHelper;
28import android.database.sqlite.SQLiteQueryBuilder;
29import android.net.Uri;
30import android.provider.Contacts;
31import android.provider.Telephony;
32import android.provider.Telephony.Mms;
33import android.provider.Telephony.MmsSms;
34import android.provider.Telephony.Sms;
35import android.provider.Telephony.TextBasedSmsColumns;
36import android.provider.Telephony.Threads;
37import android.telephony.SmsManager;
38import android.telephony.SmsMessage;
39import android.text.TextUtils;
40import android.util.Config;
41import android.util.Log;
42
43import com.android.common.ArrayListCursor;
44
45import java.util.ArrayList;
46import java.util.HashMap;
47
48public class SmsProvider extends ContentProvider {
49    private static final Uri NOTIFICATION_URI = Uri.parse("content://sms");
50    private static final Uri ICC_URI = Uri.parse("content://sms/icc");
51    static final String TABLE_SMS = "sms";
52    private static final String TABLE_RAW = "raw";
53    private static final String TABLE_SR_PENDING = "sr_pending";
54    private static final String TABLE_WORDS = "words";
55
56    private static final Integer ONE = Integer.valueOf(1);
57
58    private static final String[] CONTACT_QUERY_PROJECTION =
59            new String[] { Contacts.Phones.PERSON_ID };
60    private static final int PERSON_ID_COLUMN = 0;
61
62    /**
63     * These are the columns that are available when reading SMS
64     * messages from the ICC.  Columns whose names begin with "is_"
65     * have either "true" or "false" as their values.
66     */
67    private final static String[] ICC_COLUMNS = new String[] {
68        // N.B.: These columns must appear in the same order as the
69        // calls to add appear in convertIccToSms.
70        "service_center_address",       // getServiceCenterAddress
71        "address",                      // getDisplayOriginatingAddress
72        "message_class",                // getMessageClass
73        "body",                         // getDisplayMessageBody
74        "date",                         // getTimestampMillis
75        "status",                       // getStatusOnIcc
76        "index_on_icc",                 // getIndexOnIcc
77        "is_status_report",             // isStatusReportMessage
78        "transport_type",               // Always "sms".
79        "type",                         // Always MESSAGE_TYPE_ALL.
80        "locked",                       // Always 0 (false).
81        "error_code"                    // Always 0
82    };
83
84    @Override
85    public boolean onCreate() {
86        mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
87        return true;
88    }
89
90    @Override
91    public Cursor query(Uri url, String[] projectionIn, String selection,
92            String[] selectionArgs, String sort) {
93        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
94
95        // Generate the body of the query.
96        int match = sURLMatcher.match(url);
97        switch (match) {
98            case SMS_ALL:
99                constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL);
100                break;
101
102            case SMS_UNDELIVERED:
103                constructQueryForUndelivered(qb);
104                break;
105
106            case SMS_FAILED:
107                constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED);
108                break;
109
110            case SMS_QUEUED:
111                constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED);
112                break;
113
114            case SMS_INBOX:
115                constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX);
116                break;
117
118            case SMS_SENT:
119                constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT);
120                break;
121
122            case SMS_DRAFT:
123                constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT);
124                break;
125
126            case SMS_OUTBOX:
127                constructQueryForBox(qb, Sms.MESSAGE_TYPE_OUTBOX);
128                break;
129
130            case SMS_ALL_ID:
131                qb.setTables(TABLE_SMS);
132                qb.appendWhere("(_id = " + url.getPathSegments().get(0) + ")");
133                break;
134
135            case SMS_INBOX_ID:
136            case SMS_FAILED_ID:
137            case SMS_SENT_ID:
138            case SMS_DRAFT_ID:
139            case SMS_OUTBOX_ID:
140                qb.setTables(TABLE_SMS);
141                qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
142                break;
143
144            case SMS_CONVERSATIONS_ID:
145                int threadID;
146
147                try {
148                    threadID = Integer.parseInt(url.getPathSegments().get(1));
149                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
150                        Log.d(TAG, "query conversations: threadID=" + threadID);
151                    }
152                }
153                catch (Exception ex) {
154                    Log.e(TAG,
155                          "Bad conversation thread id: "
156                          + url.getPathSegments().get(1));
157                    return null;
158                }
159
160                qb.setTables(TABLE_SMS);
161                qb.appendWhere("thread_id = " + threadID);
162                break;
163
164            case SMS_CONVERSATIONS:
165                qb.setTables("sms, (SELECT thread_id AS group_thread_id, MAX(date)AS group_date,"
166                       + "COUNT(*) AS msg_count FROM sms GROUP BY thread_id) AS groups");
167                qb.appendWhere("sms.thread_id = groups.group_thread_id AND sms.date ="
168                       + "groups.group_date");
169                qb.setProjectionMap(sConversationProjectionMap);
170                break;
171
172            case SMS_RAW_MESSAGE:
173                qb.setTables("raw");
174                break;
175
176            case SMS_STATUS_PENDING:
177                qb.setTables("sr_pending");
178                break;
179
180            case SMS_ATTACHMENT:
181                qb.setTables("attachments");
182                break;
183
184            case SMS_ATTACHMENT_ID:
185                qb.setTables("attachments");
186                qb.appendWhere(
187                        "(sms_id = " + url.getPathSegments().get(1) + ")");
188                break;
189
190            case SMS_QUERY_THREAD_ID:
191                qb.setTables("canonical_addresses");
192                if (projectionIn == null) {
193                    projectionIn = sIDProjection;
194                }
195                break;
196
197            case SMS_STATUS_ID:
198                qb.setTables(TABLE_SMS);
199                qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
200                break;
201
202            case SMS_ALL_ICC:
203                return getAllMessagesFromIcc();
204
205            case SMS_ICC:
206                String messageIndexString = url.getPathSegments().get(1);
207
208                return getSingleMessageFromIcc(messageIndexString);
209
210            default:
211                Log.e(TAG, "Invalid request: " + url);
212                return null;
213        }
214
215        String orderBy = null;
216
217        if (!TextUtils.isEmpty(sort)) {
218            orderBy = sort;
219        } else if (qb.getTables().equals(TABLE_SMS)) {
220            orderBy = Sms.DEFAULT_SORT_ORDER;
221        }
222
223        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
224        Cursor ret = qb.query(db, projectionIn, selection, selectionArgs,
225                              null, null, orderBy);
226
227        // TODO: Since the URLs are a mess, always use content://sms
228        ret.setNotificationUri(getContext().getContentResolver(),
229                NOTIFICATION_URI);
230        return ret;
231    }
232
233    private ArrayList<String> convertIccToSms(SmsMessage message) {
234        ArrayList result = new ArrayList();
235
236        // N.B.: These calls must appear in the same order as the
237        // columns appear in ICC_COLUMNS.
238        result.add(message.getServiceCenterAddress());
239        result.add(message.getDisplayOriginatingAddress());
240        result.add(String.valueOf(message.getMessageClass()));
241        result.add(message.getDisplayMessageBody());
242        result.add(message.getTimestampMillis());
243        result.add(Sms.STATUS_NONE);
244        result.add(message.getIndexOnIcc());
245        result.add(message.isStatusReportMessage());
246        result.add("sms");
247        result.add(TextBasedSmsColumns.MESSAGE_TYPE_ALL);
248        result.add(0);      // locked
249        result.add(0);      // error_code
250        return result;
251    }
252
253    /**
254     * Return a Cursor containing just one message from the ICC.
255     */
256    private Cursor getSingleMessageFromIcc(String messageIndexString) {
257        try {
258            int messageIndex = Integer.parseInt(messageIndexString);
259            SmsManager smsManager = SmsManager.getDefault();
260            ArrayList<SmsMessage> messages = smsManager.getAllMessagesFromIcc();
261            ArrayList<ArrayList> singleRow = new ArrayList<ArrayList>();
262
263            SmsMessage message = messages.get(messageIndex);
264            if (message == null) {
265                throw new IllegalArgumentException(
266                        "Message not retrieved. ID: " + messageIndexString);
267            }
268            singleRow.add(convertIccToSms(message));
269            return withIccNotificationUri(
270                    new ArrayListCursor(ICC_COLUMNS, singleRow));
271        } catch (NumberFormatException exception) {
272            throw new IllegalArgumentException(
273                    "Bad SMS ICC ID: " + messageIndexString);
274        }
275    }
276
277    /**
278     * Return a Cursor listing all the messages stored on the ICC.
279     */
280    private Cursor getAllMessagesFromIcc() {
281        SmsManager smsManager = SmsManager.getDefault();
282        ArrayList<SmsMessage> messages = smsManager.getAllMessagesFromIcc();
283        ArrayList<ArrayList> rows = new ArrayList<ArrayList>();
284
285        for (int count = messages.size(), i = 0; i < count; i++) {
286            SmsMessage message = messages.get(i);
287            if (message != null) {
288                rows.add(convertIccToSms(message));
289            }
290        }
291        return withIccNotificationUri(new ArrayListCursor(ICC_COLUMNS, rows));
292    }
293
294    private Cursor withIccNotificationUri(Cursor cursor) {
295        cursor.setNotificationUri(getContext().getContentResolver(), ICC_URI);
296        return cursor;
297    }
298
299    private void constructQueryForBox(SQLiteQueryBuilder qb, int type) {
300        qb.setTables(TABLE_SMS);
301
302        if (type != Sms.MESSAGE_TYPE_ALL) {
303            qb.appendWhere("type=" + type);
304        }
305    }
306
307    private void constructQueryForUndelivered(SQLiteQueryBuilder qb) {
308        qb.setTables(TABLE_SMS);
309
310        qb.appendWhere("(type=" + Sms.MESSAGE_TYPE_OUTBOX +
311                       " OR type=" + Sms.MESSAGE_TYPE_FAILED +
312                       " OR type=" + Sms.MESSAGE_TYPE_QUEUED + ")");
313    }
314
315    @Override
316    public String getType(Uri url) {
317        switch (url.getPathSegments().size()) {
318        case 0:
319            return VND_ANDROID_DIR_SMS;
320            case 1:
321                try {
322                    Integer.parseInt(url.getPathSegments().get(0));
323                    return VND_ANDROID_SMS;
324                } catch (NumberFormatException ex) {
325                    return VND_ANDROID_DIR_SMS;
326                }
327            case 2:
328                // TODO: What about "threadID"?
329                if (url.getPathSegments().get(0).equals("conversations")) {
330                    return VND_ANDROID_SMSCHAT;
331                } else {
332                    return VND_ANDROID_SMS;
333                }
334        }
335        return null;
336    }
337
338    @Override
339    public Uri insert(Uri url, ContentValues initialValues) {
340        ContentValues values;
341        long rowID;
342        int type = Sms.MESSAGE_TYPE_ALL;
343
344        int match = sURLMatcher.match(url);
345        String table = TABLE_SMS;
346
347        switch (match) {
348            case SMS_ALL:
349                Integer typeObj = initialValues.getAsInteger(Sms.TYPE);
350                if (typeObj != null) {
351                    type = typeObj.intValue();
352                } else {
353                    // default to inbox
354                    type = Sms.MESSAGE_TYPE_INBOX;
355                }
356                break;
357
358            case SMS_INBOX:
359                type = Sms.MESSAGE_TYPE_INBOX;
360                break;
361
362            case SMS_FAILED:
363                type = Sms.MESSAGE_TYPE_FAILED;
364                break;
365
366            case SMS_QUEUED:
367                type = Sms.MESSAGE_TYPE_QUEUED;
368                break;
369
370            case SMS_SENT:
371                type = Sms.MESSAGE_TYPE_SENT;
372                break;
373
374            case SMS_DRAFT:
375                type = Sms.MESSAGE_TYPE_DRAFT;
376                break;
377
378            case SMS_OUTBOX:
379                type = Sms.MESSAGE_TYPE_OUTBOX;
380                break;
381
382            case SMS_RAW_MESSAGE:
383                table = "raw";
384                break;
385
386            case SMS_STATUS_PENDING:
387                table = "sr_pending";
388                break;
389
390            case SMS_ATTACHMENT:
391                table = "attachments";
392                break;
393
394            case SMS_NEW_THREAD_ID:
395                table = "canonical_addresses";
396                break;
397
398            default:
399                Log.e(TAG, "Invalid request: " + url);
400                return null;
401        }
402
403        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
404
405        if (table.equals(TABLE_SMS)) {
406            boolean addDate = false;
407            boolean addType = false;
408
409            // Make sure that the date and type are set
410            if (initialValues == null) {
411                values = new ContentValues(1);
412                addDate = true;
413                addType = true;
414            } else {
415                values = new ContentValues(initialValues);
416
417                if (!initialValues.containsKey(Sms.DATE)) {
418                    addDate = true;
419                }
420
421                if (!initialValues.containsKey(Sms.TYPE)) {
422                    addType = true;
423                }
424            }
425
426            if (addDate) {
427                values.put(Sms.DATE, new Long(System.currentTimeMillis()));
428            }
429
430            if (addType && (type != Sms.MESSAGE_TYPE_ALL)) {
431                values.put(Sms.TYPE, Integer.valueOf(type));
432            }
433
434            // thread_id
435            Long threadId = values.getAsLong(Sms.THREAD_ID);
436            String address = values.getAsString(Sms.ADDRESS);
437
438            if (((threadId == null) || (threadId == 0)) && (address != null)) {
439                values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId(
440                                   getContext(), address));
441            }
442
443            // If this message is going in as a draft, it should replace any
444            // other draft messages in the thread.  Just delete all draft
445            // messages with this thread ID.  We could add an OR REPLACE to
446            // the insert below, but we'd have to query to find the old _id
447            // to produce a conflict anyway.
448            if (values.getAsInteger(Sms.TYPE) == Sms.MESSAGE_TYPE_DRAFT) {
449                db.delete(TABLE_SMS, "thread_id=? AND type=?",
450                        new String[] { values.getAsString(Sms.THREAD_ID),
451                                       Integer.toString(Sms.MESSAGE_TYPE_DRAFT) });
452            }
453
454            if (type == Sms.MESSAGE_TYPE_INBOX) {
455                // Look up the person if not already filled in.
456                if ((values.getAsLong(Sms.PERSON) == null) && (!TextUtils.isEmpty(address))) {
457                    Cursor cursor = null;
458                    Uri uri = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL,
459                            Uri.encode(address));
460                    try {
461                        cursor = getContext().getContentResolver().query(
462                                uri,
463                                CONTACT_QUERY_PROJECTION,
464                                null, null, null);
465
466                        if (cursor.moveToFirst()) {
467                            Long id = Long.valueOf(cursor.getLong(PERSON_ID_COLUMN));
468                            values.put(Sms.PERSON, id);
469                        }
470                    } catch (Exception ex) {
471                        Log.e(TAG, "insert: query contact uri " + uri + " caught ", ex);
472                    } finally {
473                        if (cursor != null) {
474                            cursor.close();
475                        }
476                    }
477                }
478            } else {
479                // Mark all non-inbox messages read.
480                values.put(Sms.READ, ONE);
481            }
482        } else {
483            if (initialValues == null) {
484                values = new ContentValues(1);
485            } else {
486                values = initialValues;
487            }
488        }
489
490        rowID = db.insert(table, "body", values);
491
492        // Don't use a trigger for updating the words table because of a bug
493        // in FTS3.  The bug is such that the call to get the last inserted
494        // row is incorrect.
495        if (table == TABLE_SMS) {
496            // Update the words table with a corresponding row.  The words table
497            // allows us to search for words quickly, without scanning the whole
498            // table;
499            ContentValues cv = new ContentValues();
500            cv.put(Telephony.MmsSms.WordsTable.ID, rowID);
501            cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("body"));
502            cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowID);
503            cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1);
504            db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
505        }
506        if (rowID > 0) {
507            Uri uri = Uri.parse("content://" + table + "/" + rowID);
508
509            if (Log.isLoggable(TAG, Log.VERBOSE)) {
510                Log.d(TAG, "insert " + uri + " succeeded");
511            }
512            notifyChange(uri);
513            return uri;
514        } else {
515            Log.e(TAG,"insert: failed! " + values.toString());
516        }
517
518        return null;
519    }
520
521    @Override
522    public int delete(Uri url, String where, String[] whereArgs) {
523        int count;
524        int match = sURLMatcher.match(url);
525        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
526        switch (match) {
527            case SMS_ALL:
528                count = db.delete(TABLE_SMS, where, whereArgs);
529                if (count != 0) {
530                    // Don't update threads unless something changed.
531                    MmsSmsDatabaseHelper.updateAllThreads(db, where, whereArgs);
532                }
533                break;
534
535            case SMS_ALL_ID:
536                try {
537                    int message_id = Integer.parseInt(url.getPathSegments().get(0));
538                    count = MmsSmsDatabaseHelper.deleteOneSms(db, message_id);
539                } catch (Exception e) {
540                    throw new IllegalArgumentException(
541                        "Bad message id: " + url.getPathSegments().get(0));
542                }
543                break;
544
545            case SMS_CONVERSATIONS_ID:
546                int threadID;
547
548                try {
549                    threadID = Integer.parseInt(url.getPathSegments().get(1));
550                } catch (Exception ex) {
551                    throw new IllegalArgumentException(
552                            "Bad conversation thread id: "
553                            + url.getPathSegments().get(1));
554                }
555
556                // delete the messages from the sms table
557                where = DatabaseUtils.concatenateWhere("thread_id=" + threadID, where);
558                count = db.delete(TABLE_SMS, where, whereArgs);
559                MmsSmsDatabaseHelper.updateThread(db, threadID);
560                break;
561
562            case SMS_RAW_MESSAGE:
563                count = db.delete("raw", where, whereArgs);
564                break;
565
566            case SMS_STATUS_PENDING:
567                count = db.delete("sr_pending", where, whereArgs);
568                break;
569
570            case SMS_ICC:
571                String messageIndexString = url.getPathSegments().get(1);
572
573                return deleteMessageFromIcc(messageIndexString);
574
575            default:
576                throw new IllegalArgumentException("Unknown URL");
577        }
578
579        if (count > 0) {
580            notifyChange(url);
581        }
582        return count;
583    }
584
585    /**
586     * Delete the message at index from ICC.  Return true iff
587     * successful.
588     */
589    private int deleteMessageFromIcc(String messageIndexString) {
590        SmsManager smsManager = SmsManager.getDefault();
591
592        try {
593            return smsManager.deleteMessageFromIcc(
594                    Integer.parseInt(messageIndexString))
595                    ? 1 : 0;
596        } catch (NumberFormatException exception) {
597            throw new IllegalArgumentException(
598                    "Bad SMS ICC ID: " + messageIndexString);
599        } finally {
600            ContentResolver cr = getContext().getContentResolver();
601
602            cr.notifyChange(ICC_URI, null);
603        }
604    }
605
606    @Override
607    public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
608        int count = 0;
609        String table = TABLE_SMS;
610        String extraWhere = null;
611        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
612
613        switch (sURLMatcher.match(url)) {
614            case SMS_RAW_MESSAGE:
615                table = TABLE_RAW;
616                break;
617
618            case SMS_STATUS_PENDING:
619                table = TABLE_SR_PENDING;
620                break;
621
622            case SMS_ALL:
623            case SMS_FAILED:
624            case SMS_QUEUED:
625            case SMS_INBOX:
626            case SMS_SENT:
627            case SMS_DRAFT:
628            case SMS_OUTBOX:
629            case SMS_CONVERSATIONS:
630                break;
631
632            case SMS_ALL_ID:
633                extraWhere = "_id=" + url.getPathSegments().get(0);
634                break;
635
636            case SMS_INBOX_ID:
637            case SMS_FAILED_ID:
638            case SMS_SENT_ID:
639            case SMS_DRAFT_ID:
640            case SMS_OUTBOX_ID:
641                extraWhere = "_id=" + url.getPathSegments().get(1);
642                break;
643
644            case SMS_CONVERSATIONS_ID: {
645                String threadId = url.getPathSegments().get(1);
646
647                try {
648                    Integer.parseInt(threadId);
649                } catch (Exception ex) {
650                    Log.e(TAG, "Bad conversation thread id: " + threadId);
651                    break;
652                }
653
654                extraWhere = "thread_id=" + threadId;
655                break;
656            }
657
658            case SMS_STATUS_ID:
659                extraWhere = "_id=" + url.getPathSegments().get(1);
660                break;
661
662            default:
663                throw new UnsupportedOperationException(
664                        "URI " + url + " not supported");
665        }
666
667        where = DatabaseUtils.concatenateWhere(where, extraWhere);
668        count = db.update(table, values, where, whereArgs);
669
670        if (count > 0) {
671            if (Log.isLoggable(TAG, Log.VERBOSE)) {
672                Log.d(TAG, "update " + url + " succeeded");
673            }
674            notifyChange(url);
675        }
676        return count;
677    }
678
679    private void notifyChange(Uri uri) {
680        ContentResolver cr = getContext().getContentResolver();
681        cr.notifyChange(uri, null);
682        cr.notifyChange(MmsSms.CONTENT_URI, null);
683        cr.notifyChange(Uri.parse("content://mms-sms/conversations/"), null);
684    }
685
686    private SQLiteOpenHelper mOpenHelper;
687
688    private final static String TAG = "SmsProvider";
689    private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms";
690    private final static String VND_ANDROID_SMSCHAT =
691            "vnd.android.cursor.item/sms-chat";
692    private final static String VND_ANDROID_DIR_SMS =
693            "vnd.android.cursor.dir/sms";
694
695    private static final HashMap<String, String> sConversationProjectionMap =
696            new HashMap<String, String>();
697    private static final String[] sIDProjection = new String[] { "_id" };
698
699    private static final int SMS_ALL = 0;
700    private static final int SMS_ALL_ID = 1;
701    private static final int SMS_INBOX = 2;
702    private static final int SMS_INBOX_ID = 3;
703    private static final int SMS_SENT = 4;
704    private static final int SMS_SENT_ID = 5;
705    private static final int SMS_DRAFT = 6;
706    private static final int SMS_DRAFT_ID = 7;
707    private static final int SMS_OUTBOX = 8;
708    private static final int SMS_OUTBOX_ID = 9;
709    private static final int SMS_CONVERSATIONS = 10;
710    private static final int SMS_CONVERSATIONS_ID = 11;
711    private static final int SMS_RAW_MESSAGE = 15;
712    private static final int SMS_ATTACHMENT = 16;
713    private static final int SMS_ATTACHMENT_ID = 17;
714    private static final int SMS_NEW_THREAD_ID = 18;
715    private static final int SMS_QUERY_THREAD_ID = 19;
716    private static final int SMS_STATUS_ID = 20;
717    private static final int SMS_STATUS_PENDING = 21;
718    private static final int SMS_ALL_ICC = 22;
719    private static final int SMS_ICC = 23;
720    private static final int SMS_FAILED = 24;
721    private static final int SMS_FAILED_ID = 25;
722    private static final int SMS_QUEUED = 26;
723    private static final int SMS_UNDELIVERED = 27;
724
725    private static final UriMatcher sURLMatcher =
726            new UriMatcher(UriMatcher.NO_MATCH);
727
728    static {
729        sURLMatcher.addURI("sms", null, SMS_ALL);
730        sURLMatcher.addURI("sms", "#", SMS_ALL_ID);
731        sURLMatcher.addURI("sms", "inbox", SMS_INBOX);
732        sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID);
733        sURLMatcher.addURI("sms", "sent", SMS_SENT);
734        sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID);
735        sURLMatcher.addURI("sms", "draft", SMS_DRAFT);
736        sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID);
737        sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX);
738        sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID);
739        sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED);
740        sURLMatcher.addURI("sms", "failed", SMS_FAILED);
741        sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID);
742        sURLMatcher.addURI("sms", "queued", SMS_QUEUED);
743        sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS);
744        sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID);
745        sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE);
746        sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT);
747        sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID);
748        sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID);
749        sURLMatcher.addURI("sms", "threadID/*", SMS_QUERY_THREAD_ID);
750        sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID);
751        sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING);
752        sURLMatcher.addURI("sms", "icc", SMS_ALL_ICC);
753        sURLMatcher.addURI("sms", "icc/#", SMS_ICC);
754        //we keep these for not breaking old applications
755        sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
756        sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
757
758        sConversationProjectionMap.put(Sms.Conversations.SNIPPET,
759            "sms.body AS snippet");
760        sConversationProjectionMap.put(Sms.Conversations.THREAD_ID,
761            "sms.thread_id AS thread_id");
762        sConversationProjectionMap.put(Sms.Conversations.MESSAGE_COUNT,
763            "groups.msg_count AS msg_count");
764        sConversationProjectionMap.put("delta", null);
765    }
766}
767