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