1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.providers.telephony;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.database.Cursor;
25 import android.database.sqlite.SQLiteDatabase;
26 import android.database.sqlite.SQLiteOpenHelper;
27 import android.os.storage.StorageManager;
28 import android.provider.BaseColumns;
29 import android.provider.Telephony;
30 import android.provider.Telephony.Mms;
31 import android.provider.Telephony.Mms.Addr;
32 import android.provider.Telephony.Mms.Part;
33 import android.provider.Telephony.Mms.Rate;
34 import android.provider.Telephony.MmsSms;
35 import android.provider.Telephony.MmsSms.PendingMessages;
36 import android.provider.Telephony.Sms;
37 import android.provider.Telephony.Threads;
38 import android.telephony.SubscriptionManager;
39 import android.util.Log;
40 
41 import com.google.android.mms.pdu.EncodedStringValue;
42 import com.google.android.mms.pdu.PduHeaders;
43 
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 
52 /**
53  * A {@link SQLiteOpenHelper} that handles DB management of SMS and MMS tables.
54  *
55  * From N, SMS and MMS tables are split into two groups with different levels of encryption.
56  *   - the raw table, which lives inside DE(Device Encrypted) storage.
57  *   - all other tables, which lives under CE(Credential Encrypted) storage.
58  *
59  * All tables are created by this class in the same database that can live either in DE or CE
60  * storage. But not all tables in the same database should be used. Only DE tables should be used
61  * in the database created in DE and only CE tables should be used in the database created in CE.
62  * The only exception is a non-FBE device migrating from M to N, in which case the DE and CE tables
63  * will actually live inside the same storage/database.
64  *
65  * This class provides methods to create instances that manage databases in different storage.
66  * It's the responsibility of the clients of this class to make sure the right instance is
67  * used to access tables that are supposed to live inside the intended storage.
68  */
69 public class MmsSmsDatabaseHelper extends SQLiteOpenHelper {
70     private static final String TAG = "MmsSmsDatabaseHelper";
71 
72     private static final String SMS_UPDATE_THREAD_READ_BODY =
73                         "  UPDATE threads SET read = " +
74                         "    CASE (SELECT COUNT(*)" +
75                         "          FROM sms" +
76                         "          WHERE " + Sms.READ + " = 0" +
77                         "            AND " + Sms.THREAD_ID + " = threads._id)" +
78                         "      WHEN 0 THEN 1" +
79                         "      ELSE 0" +
80                         "    END" +
81                         "  WHERE threads._id = new." + Sms.THREAD_ID + "; ";
82 
83     private static final String UPDATE_THREAD_COUNT_ON_NEW =
84                         "  UPDATE threads SET message_count = " +
85                         "     (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
86                         "      ON threads._id = " + Sms.THREAD_ID +
87                         "      WHERE " + Sms.THREAD_ID + " = new.thread_id" +
88                         "        AND sms." + Sms.TYPE + " != 3) + " +
89                         "     (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
90                         "      ON threads._id = " + Mms.THREAD_ID +
91                         "      WHERE " + Mms.THREAD_ID + " = new.thread_id" +
92                         "        AND (m_type=132 OR m_type=130 OR m_type=128)" +
93                         "        AND " + Mms.MESSAGE_BOX + " != 3) " +
94                         "  WHERE threads._id = new.thread_id; ";
95 
96     private static final String UPDATE_THREAD_COUNT_ON_OLD =
97                         "  UPDATE threads SET message_count = " +
98                         "     (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
99                         "      ON threads._id = " + Sms.THREAD_ID +
100                         "      WHERE " + Sms.THREAD_ID + " = old.thread_id" +
101                         "        AND sms." + Sms.TYPE + " != 3) + " +
102                         "     (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
103                         "      ON threads._id = " + Mms.THREAD_ID +
104                         "      WHERE " + Mms.THREAD_ID + " = old.thread_id" +
105                         "        AND (m_type=132 OR m_type=130 OR m_type=128)" +
106                         "        AND " + Mms.MESSAGE_BOX + " != 3) " +
107                         "  WHERE threads._id = old.thread_id; ";
108 
109     private static final String SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE =
110                         "BEGIN" +
111                         "  UPDATE threads SET" +
112                         "    date = (strftime('%s','now') * 1000), " +
113                         "    snippet = new." + Sms.BODY + ", " +
114                         "    snippet_cs = 0" +
115                         "  WHERE threads._id = new." + Sms.THREAD_ID + "; " +
116                         UPDATE_THREAD_COUNT_ON_NEW +
117                         SMS_UPDATE_THREAD_READ_BODY +
118                         "END;";
119 
120     private static final String PDU_UPDATE_THREAD_CONSTRAINTS =
121                         "  WHEN new." + Mms.MESSAGE_TYPE + "=" +
122                         PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF +
123                         "    OR new." + Mms.MESSAGE_TYPE + "=" +
124                         PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND +
125                         "    OR new." + Mms.MESSAGE_TYPE + "=" +
126                         PduHeaders.MESSAGE_TYPE_SEND_REQ + " ";
127 
128     // When looking in the pdu table for unread messages, only count messages that
129     // are displayed to the user. The constants are defined in PduHeaders and could be used
130     // here, but the string "(m_type=132 OR m_type=130 OR m_type=128)" is used throughout this
131     // file and so it is used here to be consistent.
132     //     m_type=128   = MESSAGE_TYPE_SEND_REQ
133     //     m_type=130   = MESSAGE_TYPE_NOTIFICATION_IND
134     //     m_type=132   = MESSAGE_TYPE_RETRIEVE_CONF
135     private static final String PDU_UPDATE_THREAD_READ_BODY =
136                         "  UPDATE threads SET read = " +
137                         "    CASE (SELECT COUNT(*)" +
138                         "          FROM " + MmsProvider.TABLE_PDU +
139                         "          WHERE " + Mms.READ + " = 0" +
140                         "            AND " + Mms.THREAD_ID + " = threads._id " +
141                         "            AND (m_type=132 OR m_type=130 OR m_type=128)) " +
142                         "      WHEN 0 THEN 1" +
143                         "      ELSE 0" +
144                         "    END" +
145                         "  WHERE threads._id = new." + Mms.THREAD_ID + "; ";
146 
147     private static final String PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE =
148                         "BEGIN" +
149                         "  UPDATE threads SET" +
150                         "    date = (strftime('%s','now') * 1000), " +
151                         "    snippet = new." + Mms.SUBJECT + ", " +
152                         "    snippet_cs = new." + Mms.SUBJECT_CHARSET +
153                         "  WHERE threads._id = new." + Mms.THREAD_ID + "; " +
154                         UPDATE_THREAD_COUNT_ON_NEW +
155                         PDU_UPDATE_THREAD_READ_BODY +
156                         "END;";
157 
158     private static final String UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE =
159                         "  UPDATE threads SET snippet = " +
160                         "   (SELECT snippet FROM" +
161                         "     (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" +
162                         "      UNION SELECT date, body AS snippet, thread_id FROM sms)" +
163                         "    WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
164                         "  WHERE threads._id = OLD.thread_id; " +
165                         "  UPDATE threads SET snippet_cs = " +
166                         "   (SELECT snippet_cs FROM" +
167                         "     (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" +
168                         "      UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" +
169                         "    WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
170                         "  WHERE threads._id = OLD.thread_id; ";
171 
172 
173     // When a part is inserted, if it is not text/plain or application/smil
174     // (which both can exist with text-only MMSes), then there is an attachment.
175     // Set has_attachment=1 in the threads table for the thread in question.
176     private static final String PART_UPDATE_THREADS_ON_INSERT_TRIGGER =
177                         "CREATE TRIGGER update_threads_on_insert_part " +
178                         " AFTER INSERT ON part " +
179                         " WHEN new.ct != 'text/plain' AND new.ct != 'application/smil' " +
180                         " BEGIN " +
181                         "  UPDATE threads SET has_attachment=1 WHERE _id IN " +
182                         "   (SELECT pdu.thread_id FROM part JOIN pdu ON pdu._id=part.mid " +
183                         "     WHERE part._id=new._id LIMIT 1); " +
184                         " END";
185 
186     // When the 'mid' column in the part table is updated, we need to run the trigger to update
187     // the threads table's has_attachment column, if the part is an attachment.
188     private static final String PART_UPDATE_THREADS_ON_UPDATE_TRIGGER =
189                         "CREATE TRIGGER update_threads_on_update_part " +
190                         " AFTER UPDATE of " + Part.MSG_ID + " ON part " +
191                         " WHEN new.ct != 'text/plain' AND new.ct != 'application/smil' " +
192                         " BEGIN " +
193                         "  UPDATE threads SET has_attachment=1 WHERE _id IN " +
194                         "   (SELECT pdu.thread_id FROM part JOIN pdu ON pdu._id=part.mid " +
195                         "     WHERE part._id=new._id LIMIT 1); " +
196                         " END";
197 
198 
199     // When a part is deleted (with the same non-text/SMIL constraint as when
200     // we set has_attachment), update the threads table for all threads.
201     // Unfortunately we cannot update only the thread that the part was
202     // attached to, as it is possible that the part has been orphaned and
203     // the message it was attached to is already gone.
204     private static final String PART_UPDATE_THREADS_ON_DELETE_TRIGGER =
205                         "CREATE TRIGGER update_threads_on_delete_part " +
206                         " AFTER DELETE ON part " +
207                         " WHEN old.ct != 'text/plain' AND old.ct != 'application/smil' " +
208                         " BEGIN " +
209                         "  UPDATE threads SET has_attachment = " +
210                         "   CASE " +
211                         "    (SELECT COUNT(*) FROM part JOIN pdu " +
212                         "     WHERE pdu.thread_id = threads._id " +
213                         "     AND part.ct != 'text/plain' AND part.ct != 'application/smil' " +
214                         "     AND part.mid = pdu._id)" +
215                         "   WHEN 0 THEN 0 " +
216                         "   ELSE 1 " +
217                         "   END; " +
218                         " END";
219 
220     // When the 'thread_id' column in the pdu table is updated, we need to run the trigger to update
221     // the threads table's has_attachment column, if the message has an attachment in 'part' table
222     private static final String PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER =
223                         "CREATE TRIGGER update_threads_on_update_pdu " +
224                         " AFTER UPDATE of thread_id ON pdu " +
225                         " BEGIN " +
226                         "  UPDATE threads SET has_attachment=1 WHERE _id IN " +
227                         "   (SELECT pdu.thread_id FROM part JOIN pdu " +
228                         "     WHERE part.ct != 'text/plain' AND part.ct != 'application/smil' " +
229                         "     AND part.mid = pdu._id);" +
230                         " END";
231 
232     private static MmsSmsDatabaseHelper sDeInstance = null;
233     private static MmsSmsDatabaseHelper sCeInstance = null;
234     private static boolean sTriedAutoIncrement = false;
235     private static boolean sFakeLowStorageTest = false;     // for testing only
236 
237     static final String DATABASE_NAME = "mmssms.db";
238     static final int DATABASE_VERSION = 64;
239     private final Context mContext;
240     private LowStorageMonitor mLowStorageMonitor;
241 
242 
MmsSmsDatabaseHelper(Context context)243     private MmsSmsDatabaseHelper(Context context) {
244         super(context, DATABASE_NAME, null, DATABASE_VERSION);
245 
246         mContext = context;
247     }
248 
249     /**
250      * Returns a singleton helper for the combined MMS and SMS database in device encrypted storage.
251      */
getInstanceForDe(Context context)252     /* package */ static synchronized MmsSmsDatabaseHelper getInstanceForDe(Context context) {
253         if (sDeInstance == null) {
254             sDeInstance = new MmsSmsDatabaseHelper(ProviderUtil.getDeviceEncryptedContext(context));
255         }
256         return sDeInstance;
257     }
258 
259     /**
260      * Returns a singleton helper for the combined MMS and SMS database in credential encrypted
261      * storage. If FBE is not available, use the device encrypted storage instead.
262      */
getInstanceForCe(Context context)263     /* package */ static synchronized MmsSmsDatabaseHelper getInstanceForCe(Context context) {
264         if (sCeInstance == null) {
265             if (StorageManager.isFileEncryptedNativeOrEmulated()) {
266                 sCeInstance = new MmsSmsDatabaseHelper(
267                     ProviderUtil.getCredentialEncryptedContext(context));
268             } else {
269                 sCeInstance = getInstanceForDe(context);
270             }
271         }
272         return sCeInstance;
273     }
274 
275     /**
276      * Look through all the recipientIds referenced by the threads and then delete any
277      * unreferenced rows from the canonical_addresses table.
278      */
removeUnferencedCanonicalAddresses(SQLiteDatabase db)279     private static void removeUnferencedCanonicalAddresses(SQLiteDatabase db) {
280         Cursor c = db.query(MmsSmsProvider.TABLE_THREADS, new String[] { "recipient_ids" },
281                 null, null, null, null, null);
282         if (c != null) {
283             try {
284                 if (c.getCount() == 0) {
285                     // no threads, delete all addresses
286                     int rows = db.delete("canonical_addresses", null, null);
287                 } else {
288                     // Find all the referenced recipient_ids from the threads. recipientIds is
289                     // a space-separated list of recipient ids: "1 14 21"
290                     HashSet<Integer> recipientIds = new HashSet<Integer>();
291                     while (c.moveToNext()) {
292                         String[] recips = c.getString(0).split(" ");
293                         for (String recip : recips) {
294                             try {
295                                 int recipientId = Integer.parseInt(recip);
296                                 recipientIds.add(recipientId);
297                             } catch (Exception e) {
298                             }
299                         }
300                     }
301                     // Now build a selection string of all the unique recipient ids
302                     StringBuilder sb = new StringBuilder();
303                     Iterator<Integer> iter = recipientIds.iterator();
304                     sb.append("_id NOT IN (");
305                     while (iter.hasNext()) {
306                         sb.append(iter.next());
307                         if (iter.hasNext()) {
308                             sb.append(",");
309                         }
310                     }
311                     sb.append(")");
312                     int rows = db.delete("canonical_addresses", sb.toString(), null);
313                 }
314             } finally {
315                 c.close();
316             }
317         }
318     }
319 
updateThread(SQLiteDatabase db, long thread_id)320     public static void updateThread(SQLiteDatabase db, long thread_id) {
321         if (thread_id < 0) {
322             updateAllThreads(db, null, null);
323             return;
324         }
325 
326         db.beginTransaction();
327         try {
328             // Delete the row for this thread in the threads table if
329             // there are no more messages attached to it in either
330             // the sms or pdu tables.
331             int rows = db.delete(MmsSmsProvider.TABLE_THREADS,
332                       "_id = ? AND _id NOT IN" +
333                       "          (SELECT thread_id FROM sms " +
334                       "           UNION SELECT thread_id FROM pdu)",
335                       new String[] { String.valueOf(thread_id) });
336             if (rows > 0) {
337                 // If this deleted a row, let's remove orphaned canonical_addresses and get outta here
338                 removeUnferencedCanonicalAddresses(db);
339             } else {
340                 // Update the message count in the threads table as the sum
341                 // of all messages in both the sms and pdu tables.
342                 db.execSQL(
343                         "  UPDATE threads SET message_count = " +
344                                 "     (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
345                                 "      ON threads._id = " + Sms.THREAD_ID +
346                                 "      WHERE " + Sms.THREAD_ID + " = " + thread_id +
347                                 "        AND sms." + Sms.TYPE + " != 3) + " +
348                                 "     (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
349                                 "      ON threads._id = " + Mms.THREAD_ID +
350                                 "      WHERE " + Mms.THREAD_ID + " = " + thread_id +
351                                 "        AND (m_type=132 OR m_type=130 OR m_type=128)" +
352                                 "        AND " + Mms.MESSAGE_BOX + " != 3) " +
353                                 "  WHERE threads._id = " + thread_id + ";");
354 
355                 // Update the date and the snippet (and its character set) in
356                 // the threads table to be that of the most recent message in
357                 // the thread.
358                 db.execSQL(
359                 "  UPDATE threads" +
360                 "  SET" +
361                 "  date =" +
362                 "    (SELECT date FROM" +
363                 "        (SELECT date * 1000 AS date, thread_id FROM pdu" +
364                 "         UNION SELECT date, thread_id FROM sms)" +
365                 "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," +
366                 "  snippet =" +
367                 "    (SELECT snippet FROM" +
368                 "        (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" +
369                 "         UNION SELECT date, body AS snippet, thread_id FROM sms)" +
370                 "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," +
371                 "  snippet_cs =" +
372                 "    (SELECT snippet_cs FROM" +
373                 "        (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" +
374                 "         UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" +
375                 "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" +
376                 "  WHERE threads._id = " + thread_id + ";");
377 
378                 // Update the error column of the thread to indicate if there
379                 // are any messages in it that have failed to send.
380                 // First check to see if there are any messages with errors in this thread.
381                 String query = "SELECT thread_id FROM sms WHERE type=" +
382                         Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED +
383                         " AND thread_id = " + thread_id +
384                         " LIMIT 1";
385                 int setError = 0;
386                 Cursor c = db.rawQuery(query, null);
387                 if (c != null) {
388                     try {
389                         setError = c.getCount();    // Because of the LIMIT 1, count will be 1 or 0.
390                     } finally {
391                         c.close();
392                     }
393                 }
394                 // What's the current state of the error flag in the threads table?
395                 String errorQuery = "SELECT error FROM threads WHERE _id = " + thread_id;
396                 c = db.rawQuery(errorQuery, null);
397                 if (c != null) {
398                     try {
399                         if (c.moveToNext()) {
400                             int curError = c.getInt(0);
401                             if (curError != setError) {
402                                 // The current thread error column differs, update it.
403                                 db.execSQL("UPDATE threads SET error=" + setError +
404                                         " WHERE _id = " + thread_id);
405                             }
406                         }
407                     } finally {
408                         c.close();
409                     }
410                 }
411             }
412             db.setTransactionSuccessful();
413         } catch (Throwable ex) {
414             Log.e(TAG, ex.getMessage(), ex);
415         } finally {
416             db.endTransaction();
417         }
418     }
419 
updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs)420     public static void updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs) {
421         db.beginTransaction();
422         try {
423             if (where == null) {
424                 where = "";
425             } else {
426                 where = "WHERE (" + where + ")";
427             }
428             String query = "SELECT _id FROM threads WHERE _id IN " +
429                            "(SELECT DISTINCT thread_id FROM sms " + where + ")";
430             Cursor c = db.rawQuery(query, whereArgs);
431             if (c != null) {
432                 try {
433                     while (c.moveToNext()) {
434                         updateThread(db, c.getInt(0));
435                     }
436                 } finally {
437                     c.close();
438                 }
439             }
440             // TODO: there are several db operations in this function. Lets wrap them in a
441             // transaction to make it faster.
442             // remove orphaned threads
443             db.delete(MmsSmsProvider.TABLE_THREADS,
444                     "_id NOT IN (SELECT DISTINCT thread_id FROM sms where thread_id NOT NULL " +
445                     "UNION SELECT DISTINCT thread_id FROM pdu where thread_id NOT NULL)", null);
446 
447             // remove orphaned canonical_addresses
448             removeUnferencedCanonicalAddresses(db);
449 
450             db.setTransactionSuccessful();
451         } catch (Throwable ex) {
452             Log.e(TAG, ex.getMessage(), ex);
453         } finally {
454             db.endTransaction();
455         }
456     }
457 
deleteOneSms(SQLiteDatabase db, int message_id)458     public static int deleteOneSms(SQLiteDatabase db, int message_id) {
459         int thread_id = -1;
460         // Find the thread ID that the specified SMS belongs to.
461         Cursor c = db.query("sms", new String[] { "thread_id" },
462                             "_id=" + message_id, null, null, null, null);
463         if (c != null) {
464             if (c.moveToFirst()) {
465                 thread_id = c.getInt(0);
466             }
467             c.close();
468         }
469 
470         // Delete the specified message.
471         int rows = db.delete("sms", "_id=" + message_id, null);
472         if (thread_id > 0) {
473             // Update its thread.
474             updateThread(db, thread_id);
475         }
476         return rows;
477     }
478 
479     @Override
onCreate(SQLiteDatabase db)480     public void onCreate(SQLiteDatabase db) {
481         createMmsTables(db);
482         createSmsTables(db);
483         createCommonTables(db);
484         createCommonTriggers(db);
485         createMmsTriggers(db);
486         createWordsTables(db);
487         createIndices(db);
488     }
489 
490     // When upgrading the database we need to populate the words
491     // table with the rows out of sms and part.
populateWordsTable(SQLiteDatabase db)492     private void populateWordsTable(SQLiteDatabase db) {
493         final String TABLE_WORDS = "words";
494         {
495             Cursor smsRows = db.query(
496                     "sms",
497                     new String[] { Sms._ID, Sms.BODY },
498                     null,
499                     null,
500                     null,
501                     null,
502                     null);
503             try {
504                 if (smsRows != null) {
505                     smsRows.moveToPosition(-1);
506                     ContentValues cv = new ContentValues();
507                     while (smsRows.moveToNext()) {
508                         cv.clear();
509 
510                         long id = smsRows.getLong(0);        // 0 for Sms._ID
511                         String body = smsRows.getString(1);  // 1 for Sms.BODY
512 
513                         cv.put(Telephony.MmsSms.WordsTable.ID, id);
514                         cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body);
515                         cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id);
516                         cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1);
517                         db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
518                     }
519                 }
520             } finally {
521                 if (smsRows != null) {
522                     smsRows.close();
523                 }
524             }
525         }
526 
527         {
528             Cursor mmsRows = db.query(
529                     "part",
530                     new String[] { Part._ID, Part.TEXT },
531                     "ct = 'text/plain'",
532                     null,
533                     null,
534                     null,
535                     null);
536             try {
537                 if (mmsRows != null) {
538                     mmsRows.moveToPosition(-1);
539                     ContentValues cv = new ContentValues();
540                     while (mmsRows.moveToNext()) {
541                         cv.clear();
542 
543                         long id = mmsRows.getLong(0);         // 0 for Part._ID
544                         String body = mmsRows.getString(1);   // 1 for Part.TEXT
545 
546                         cv.put(Telephony.MmsSms.WordsTable.ID, id);
547                         cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body);
548                         cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id);
549                         cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1);
550                         db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
551                     }
552                 }
553             } finally {
554                 if (mmsRows != null) {
555                     mmsRows.close();
556                 }
557             }
558         }
559     }
560 
createWordsTables(SQLiteDatabase db)561     private void createWordsTables(SQLiteDatabase db) {
562         try {
563             db.execSQL("CREATE VIRTUAL TABLE words USING FTS3 (_id INTEGER PRIMARY KEY, index_text TEXT, source_id INTEGER, table_to_use INTEGER);");
564 
565             // monitor the sms table
566             // NOTE don't handle inserts using a trigger because it has an unwanted
567             // side effect:  the value returned for the last row ends up being the
568             // id of one of the trigger insert not the original row insert.
569             // Handle inserts manually in the provider.
570             db.execSQL("CREATE TRIGGER sms_words_update AFTER UPDATE ON sms BEGIN UPDATE words " +
571                     " SET index_text = NEW.body WHERE (source_id=NEW._id AND table_to_use=1); " +
572                     " END;");
573             db.execSQL("CREATE TRIGGER sms_words_delete AFTER DELETE ON sms BEGIN DELETE FROM " +
574                     "  words WHERE source_id = OLD._id AND table_to_use = 1; END;");
575 
576             populateWordsTable(db);
577         } catch (Exception ex) {
578             Log.e(TAG, "got exception creating words table: " + ex.toString());
579         }
580     }
581 
createIndices(SQLiteDatabase db)582     private void createIndices(SQLiteDatabase db) {
583         createThreadIdIndex(db);
584     }
585 
createThreadIdIndex(SQLiteDatabase db)586     private void createThreadIdIndex(SQLiteDatabase db) {
587         try {
588             db.execSQL("CREATE INDEX IF NOT EXISTS typeThreadIdIndex ON sms" +
589             " (type, thread_id);");
590         } catch (Exception ex) {
591             Log.e(TAG, "got exception creating indices: " + ex.toString());
592         }
593     }
594 
createMmsTables(SQLiteDatabase db)595     private void createMmsTables(SQLiteDatabase db) {
596         // N.B.: Whenever the columns here are changed, the columns in
597         // {@ref MmsSmsProvider} must be changed to match.
598         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" +
599                    Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
600                    Mms.THREAD_ID + " INTEGER," +
601                    Mms.DATE + " INTEGER," +
602                    Mms.DATE_SENT + " INTEGER DEFAULT 0," +
603                    Mms.MESSAGE_BOX + " INTEGER," +
604                    Mms.READ + " INTEGER DEFAULT 0," +
605                    Mms.MESSAGE_ID + " TEXT," +
606                    Mms.SUBJECT + " TEXT," +
607                    Mms.SUBJECT_CHARSET + " INTEGER," +
608                    Mms.CONTENT_TYPE + " TEXT," +
609                    Mms.CONTENT_LOCATION + " TEXT," +
610                    Mms.EXPIRY + " INTEGER," +
611                    Mms.MESSAGE_CLASS + " TEXT," +
612                    Mms.MESSAGE_TYPE + " INTEGER," +
613                    Mms.MMS_VERSION + " INTEGER," +
614                    Mms.MESSAGE_SIZE + " INTEGER," +
615                    Mms.PRIORITY + " INTEGER," +
616                    Mms.READ_REPORT + " INTEGER," +
617                    Mms.REPORT_ALLOWED + " INTEGER," +
618                    Mms.RESPONSE_STATUS + " INTEGER," +
619                    Mms.STATUS + " INTEGER," +
620                    Mms.TRANSACTION_ID + " TEXT," +
621                    Mms.RETRIEVE_STATUS + " INTEGER," +
622                    Mms.RETRIEVE_TEXT + " TEXT," +
623                    Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," +
624                    Mms.READ_STATUS + " INTEGER," +
625                    Mms.CONTENT_CLASS + " INTEGER," +
626                    Mms.RESPONSE_TEXT + " TEXT," +
627                    Mms.DELIVERY_TIME + " INTEGER," +
628                    Mms.DELIVERY_REPORT + " INTEGER," +
629                    Mms.LOCKED + " INTEGER DEFAULT 0," +
630                    Mms.SUBSCRIPTION_ID + " INTEGER DEFAULT "
631                            + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
632                    Mms.SEEN + " INTEGER DEFAULT 0," +
633                    Mms.CREATOR + " TEXT," +
634                    Mms.TEXT_ONLY + " INTEGER DEFAULT 0" +
635                    ");");
636 
637         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" +
638                    Addr._ID + " INTEGER PRIMARY KEY," +
639                    Addr.MSG_ID + " INTEGER," +
640                    Addr.CONTACT_ID + " INTEGER," +
641                    Addr.ADDRESS + " TEXT," +
642                    Addr.TYPE + " INTEGER," +
643                    Addr.CHARSET + " INTEGER);");
644 
645         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PART + " (" +
646                    Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
647                    Part.MSG_ID + " INTEGER," +
648                    Part.SEQ + " INTEGER DEFAULT 0," +
649                    Part.CONTENT_TYPE + " TEXT," +
650                    Part.NAME + " TEXT," +
651                    Part.CHARSET + " INTEGER," +
652                    Part.CONTENT_DISPOSITION + " TEXT," +
653                    Part.FILENAME + " TEXT," +
654                    Part.CONTENT_ID + " TEXT," +
655                    Part.CONTENT_LOCATION + " TEXT," +
656                    Part.CT_START + " INTEGER," +
657                    Part.CT_TYPE + " TEXT," +
658                    Part._DATA + " TEXT," +
659                    Part.TEXT + " TEXT);");
660 
661         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_RATE + " (" +
662                    Rate.SENT_TIME + " INTEGER);");
663 
664         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_DRM + " (" +
665                    BaseColumns._ID + " INTEGER PRIMARY KEY," +
666                    "_data TEXT);");
667 
668         // Restricted view of pdu table, only sent/received messages without wap pushes
669         db.execSQL("CREATE VIEW " + MmsProvider.VIEW_PDU_RESTRICTED + " AS " +
670                 "SELECT * FROM " + MmsProvider.TABLE_PDU + " WHERE " +
671                 "(" + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_INBOX +
672                 " OR " +
673                 Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_SENT + ")" +
674                 " AND " +
675                 "(" + Mms.MESSAGE_TYPE + "!=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + ");");
676     }
677 
678     // Unlike the other trigger-creating functions, this function can be called multiple times
679     // without harm.
createMmsTriggers(SQLiteDatabase db)680     private void createMmsTriggers(SQLiteDatabase db) {
681         // Cleans up parts when a MM is deleted.
682         db.execSQL("DROP TRIGGER IF EXISTS part_cleanup");
683         db.execSQL("CREATE TRIGGER part_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " +
684                 "BEGIN " +
685                 "  DELETE FROM " + MmsProvider.TABLE_PART +
686                 "  WHERE " + Part.MSG_ID + "=old._id;" +
687                 "END;");
688 
689         // Cleans up address info when a MM is deleted.
690         db.execSQL("DROP TRIGGER IF EXISTS addr_cleanup");
691         db.execSQL("CREATE TRIGGER addr_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " +
692                 "BEGIN " +
693                 "  DELETE FROM " + MmsProvider.TABLE_ADDR +
694                 "  WHERE " + Addr.MSG_ID + "=old._id;" +
695                 "END;");
696 
697         // Delete obsolete delivery-report, read-report while deleting their
698         // associated Send.req.
699         db.execSQL("DROP TRIGGER IF EXISTS cleanup_delivery_and_read_report");
700         db.execSQL("CREATE TRIGGER cleanup_delivery_and_read_report " +
701                 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " +
702                 "WHEN old." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ + " " +
703                 "BEGIN " +
704                 "  DELETE FROM " + MmsProvider.TABLE_PDU +
705                 "  WHERE (" + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_DELIVERY_IND +
706                 "    OR " + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_ORIG_IND +
707                 ")" +
708                 "    AND " + Mms.MESSAGE_ID + "=old." + Mms.MESSAGE_ID + "; " +
709                 "END;");
710 
711         db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_insert_part");
712         db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER);
713 
714         db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_part");
715         db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER);
716 
717         db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_delete_part");
718         db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER);
719 
720         db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_pdu");
721         db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER);
722 
723         // Delete pending status for a message when it is deleted.
724         db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_delete");
725         db.execSQL("CREATE TRIGGER delete_mms_pending_on_delete " +
726                    "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " +
727                    "BEGIN " +
728                    "  DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG +
729                    "  WHERE " + PendingMessages.MSG_ID + "=old._id; " +
730                    "END;");
731 
732         // When a message is moved out of Outbox, delete its pending status.
733         db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_update");
734         db.execSQL("CREATE TRIGGER delete_mms_pending_on_update " +
735                    "AFTER UPDATE ON " + MmsProvider.TABLE_PDU + " " +
736                    "WHEN old." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX +
737                    "  AND new." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " +
738                    "BEGIN " +
739                    "  DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG +
740                    "  WHERE " + PendingMessages.MSG_ID + "=new._id; " +
741                    "END;");
742 
743         // Insert pending status for M-Notification.ind or M-ReadRec.ind
744         // when they are inserted into Inbox/Outbox.
745         db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_insert");
746         db.execSQL("CREATE TRIGGER insert_mms_pending_on_insert " +
747                    "AFTER INSERT ON pdu " +
748                    "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND +
749                    "  OR new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_REC_IND +
750                    " " +
751                    "BEGIN " +
752                    "  INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG +
753                    "    (" + PendingMessages.PROTO_TYPE + "," +
754                    "     " + PendingMessages.MSG_ID + "," +
755                    "     " + PendingMessages.MSG_TYPE + "," +
756                    "     " + PendingMessages.ERROR_TYPE + "," +
757                    "     " + PendingMessages.ERROR_CODE + "," +
758                    "     " + PendingMessages.RETRY_INDEX + "," +
759                    "     " + PendingMessages.DUE_TIME + ") " +
760                    "  VALUES " +
761                    "    (" + MmsSms.MMS_PROTO + "," +
762                    "      new." + BaseColumns._ID + "," +
763                    "      new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" +
764                    "END;");
765 
766 
767         // Insert pending status for M-Send.req when it is moved into Outbox.
768         db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_update");
769         db.execSQL("CREATE TRIGGER insert_mms_pending_on_update " +
770                    "AFTER UPDATE ON pdu " +
771                    "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ +
772                    "  AND new." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX +
773                    "  AND old." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " +
774                    "BEGIN " +
775                    "  INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG +
776                    "    (" + PendingMessages.PROTO_TYPE + "," +
777                    "     " + PendingMessages.MSG_ID + "," +
778                    "     " + PendingMessages.MSG_TYPE + "," +
779                    "     " + PendingMessages.ERROR_TYPE + "," +
780                    "     " + PendingMessages.ERROR_CODE + "," +
781                    "     " + PendingMessages.RETRY_INDEX + "," +
782                    "     " + PendingMessages.DUE_TIME + ") " +
783                    "  VALUES " +
784                    "    (" + MmsSms.MMS_PROTO + "," +
785                    "      new." + BaseColumns._ID + "," +
786                    "      new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" +
787                    "END;");
788 
789         // monitor the mms table
790         db.execSQL("DROP TRIGGER IF EXISTS mms_words_update");
791         db.execSQL("CREATE TRIGGER mms_words_update AFTER UPDATE ON part BEGIN UPDATE words " +
792                 " SET index_text = NEW.text WHERE (source_id=NEW._id AND table_to_use=2); " +
793                 " END;");
794 
795         db.execSQL("DROP TRIGGER IF EXISTS mms_words_delete");
796         db.execSQL("CREATE TRIGGER mms_words_delete AFTER DELETE ON part BEGIN DELETE FROM " +
797                 " words WHERE source_id = OLD._id AND table_to_use = 2; END;");
798 
799         // Updates threads table whenever a message in pdu is updated.
800         db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
801         db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
802                    "  UPDATE OF " + Mms.DATE + ", " + Mms.SUBJECT + ", " + Mms.MESSAGE_BOX +
803                    "  ON " + MmsProvider.TABLE_PDU + " " +
804                    PDU_UPDATE_THREAD_CONSTRAINTS +
805                    PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
806 
807         // Update threads table whenever a message in pdu is deleted
808         db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete");
809         db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " +
810                    "AFTER DELETE ON pdu " +
811                    "BEGIN " +
812                    "  UPDATE threads SET " +
813                    "     date = (strftime('%s','now') * 1000)" +
814                    "  WHERE threads._id = old." + Mms.THREAD_ID + "; " +
815                    UPDATE_THREAD_COUNT_ON_OLD +
816                    UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE +
817                    "END;");
818 
819         // Updates threads table whenever a message is added to pdu.
820         db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
821         db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " +
822                    MmsProvider.TABLE_PDU + " " +
823                    PDU_UPDATE_THREAD_CONSTRAINTS +
824                    PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
825 
826         // Updates threads table whenever a message in pdu is updated.
827         db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update");
828         db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" +
829                    "  UPDATE OF " + Mms.READ +
830                    "  ON " + MmsProvider.TABLE_PDU + " " +
831                    PDU_UPDATE_THREAD_CONSTRAINTS +
832                    "BEGIN " +
833                    PDU_UPDATE_THREAD_READ_BODY +
834                    "END;");
835 
836         // Update the error flag of threads when delete pending message.
837         db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_mms");
838         db.execSQL("CREATE TRIGGER update_threads_error_on_delete_mms " +
839                    "  BEFORE DELETE ON pdu" +
840                    "  WHEN OLD._id IN (SELECT DISTINCT msg_id" +
841                    "                   FROM pending_msgs" +
842                    "                   WHERE err_type >= 10) " +
843                    "BEGIN " +
844                    "  UPDATE threads SET error = error - 1" +
845                    "  WHERE _id = OLD.thread_id; " +
846                    "END;");
847 
848         // Update the error flag of threads while moving an MM out of Outbox,
849         // which was failed to be sent permanently.
850         db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms");
851         db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " +
852                    "  BEFORE UPDATE OF msg_box ON pdu " +
853                    "  WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " +
854                    "  AND (OLD._id IN (SELECT DISTINCT msg_id" +
855                    "                   FROM pending_msgs" +
856                    "                   WHERE err_type >= 10)) " +
857                    "BEGIN " +
858                    "  UPDATE threads SET error = error - 1" +
859                    "  WHERE _id = OLD.thread_id; " +
860                    "END;");
861     }
862 
createSmsTables(SQLiteDatabase db)863     private void createSmsTables(SQLiteDatabase db) {
864         // N.B.: Whenever the columns here are changed, the columns in
865         // {@ref MmsSmsProvider} must be changed to match.
866         db.execSQL("CREATE TABLE sms (" +
867                    "_id INTEGER PRIMARY KEY," +
868                    "thread_id INTEGER," +
869                    "address TEXT," +
870                    "person INTEGER," +
871                    "date INTEGER," +
872                    "date_sent INTEGER DEFAULT 0," +
873                    "protocol INTEGER," +
874                    "read INTEGER DEFAULT 0," +
875                    "status INTEGER DEFAULT -1," + // a TP-Status value
876                                                   // or -1 if it
877                                                   // status hasn't
878                                                   // been received
879                    "type INTEGER," +
880                    "reply_path_present INTEGER," +
881                    "subject TEXT," +
882                    "body TEXT," +
883                    "service_center TEXT," +
884                    "locked INTEGER DEFAULT 0," +
885                    "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
886                    "error_code INTEGER DEFAULT 0," +
887                    "creator TEXT," +
888                    "seen INTEGER DEFAULT 0" +
889                    ");");
890 
891         /**
892          * This table is used by the SMS dispatcher to hold
893          * incomplete partial messages until all the parts arrive.
894          */
895         db.execSQL("CREATE TABLE raw (" +
896                    "_id INTEGER PRIMARY KEY," +
897                    "date INTEGER," +
898                    "reference_number INTEGER," + // one per full message
899                    "count INTEGER," + // the number of parts
900                    "sequence INTEGER," + // the part number of this message
901                    "destination_port INTEGER," +
902                    "address TEXT," +
903                    "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
904                    "pdu TEXT," + // the raw PDU for this part
905                    "deleted INTEGER DEFAULT 0," + // bool to indicate if row is deleted
906                    "message_body TEXT);"); // message body
907 
908         db.execSQL("CREATE TABLE attachments (" +
909                    "sms_id INTEGER," +
910                    "content_url TEXT," +
911                    "offset INTEGER);");
912 
913         /**
914          * This table is used by the SMS dispatcher to hold pending
915          * delivery status report intents.
916          */
917         db.execSQL("CREATE TABLE sr_pending (" +
918                    "reference_number INTEGER," +
919                    "action TEXT," +
920                    "data TEXT);");
921 
922         // Restricted view of sms table, only sent/received messages
923         db.execSQL("CREATE VIEW " + SmsProvider.VIEW_SMS_RESTRICTED + " AS " +
924                    "SELECT * FROM " + SmsProvider.TABLE_SMS + " WHERE " +
925                    Sms.TYPE + "=" + Sms.MESSAGE_TYPE_INBOX +
926                    " OR " +
927                    Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";");
928     }
929 
createCommonTables(SQLiteDatabase db)930     private void createCommonTables(SQLiteDatabase db) {
931         // TODO Ensure that each entry is removed when the last use of
932         // any address equivalent to its address is removed.
933 
934         /**
935          * This table maps the first instance seen of any particular
936          * MMS/SMS address to an ID, which is then used as its
937          * canonical representation.  If the same address or an
938          * equivalent address (as determined by our Sqlite
939          * PHONE_NUMBERS_EQUAL extension) is seen later, this same ID
940          * will be used. The _id is created with AUTOINCREMENT so it
941          * will never be reused again if a recipient is deleted.
942          */
943         db.execSQL("CREATE TABLE canonical_addresses (" +
944                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
945                    "address TEXT);");
946 
947         /**
948          * This table maps the subject and an ordered set of recipient
949          * IDs, separated by spaces, to a unique thread ID.  The IDs
950          * come from the canonical_addresses table.  This works
951          * because messages are considered to be part of the same
952          * thread if they have the same subject (or a null subject)
953          * and the same set of recipients.
954          */
955         db.execSQL("CREATE TABLE threads (" +
956                    Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
957                    Threads.DATE + " INTEGER DEFAULT 0," +
958                    Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," +
959                    Threads.RECIPIENT_IDS + " TEXT," +
960                    Threads.SNIPPET + " TEXT," +
961                    Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," +
962                    Threads.READ + " INTEGER DEFAULT 1," +
963                    Threads.ARCHIVED + " INTEGER DEFAULT 0," +
964                    Threads.TYPE + " INTEGER DEFAULT 0," +
965                    Threads.ERROR + " INTEGER DEFAULT 0," +
966                    Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);");
967 
968         /**
969          * This table stores the queue of messages to be sent/downloaded.
970          */
971         db.execSQL("CREATE TABLE " + MmsSmsProvider.TABLE_PENDING_MSG +" (" +
972                    PendingMessages._ID + " INTEGER PRIMARY KEY," +
973                    PendingMessages.PROTO_TYPE + " INTEGER," +
974                    PendingMessages.MSG_ID + " INTEGER," +
975                    PendingMessages.MSG_TYPE + " INTEGER," +
976                    PendingMessages.ERROR_TYPE + " INTEGER," +
977                    PendingMessages.ERROR_CODE + " INTEGER," +
978                    PendingMessages.RETRY_INDEX + " INTEGER NOT NULL DEFAULT 0," +
979                    PendingMessages.DUE_TIME + " INTEGER," +
980                    PendingMessages.SUBSCRIPTION_ID + " INTEGER DEFAULT " +
981                            SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
982                    PendingMessages.LAST_TRY + " INTEGER);");
983 
984     }
985 
986     // TODO Check the query plans for these triggers.
createCommonTriggers(SQLiteDatabase db)987     private void createCommonTriggers(SQLiteDatabase db) {
988         // Updates threads table whenever a message is added to sms.
989         db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " +
990                    SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
991 
992         // Updates threads table whenever a message in sms is updated.
993         db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" +
994                    "  UPDATE OF " + Sms.DATE + ", " + Sms.BODY + ", " + Sms.TYPE +
995                    "  ON sms " +
996                    SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
997 
998         // Updates threads table whenever a message in sms is updated.
999         db.execSQL("CREATE TRIGGER sms_update_thread_read_on_update AFTER" +
1000                    "  UPDATE OF " + Sms.READ +
1001                    "  ON sms " +
1002                    "BEGIN " +
1003                    SMS_UPDATE_THREAD_READ_BODY +
1004                    "END;");
1005 
1006         // As of DATABASE_VERSION 55, we've removed these triggers that delete empty threads.
1007         // These triggers interfere with saving drafts on brand new threads. Instead of
1008         // triggers cleaning up empty threads, the empty threads should be cleaned up by
1009         // an explicit call to delete with Threads.OBSOLETE_THREADS_URI.
1010 
1011 //        // When the last message in a thread is deleted, these
1012 //        // triggers ensure that the entry for its thread ID is removed
1013 //        // from the threads table.
1014 //        db.execSQL("CREATE TRIGGER delete_obsolete_threads_pdu " +
1015 //                   "AFTER DELETE ON pdu " +
1016 //                   "BEGIN " +
1017 //                   "  DELETE FROM threads " +
1018 //                   "  WHERE " +
1019 //                   "    _id = old.thread_id " +
1020 //                   "    AND _id NOT IN " +
1021 //                   "    (SELECT thread_id FROM sms " +
1022 //                   "     UNION SELECT thread_id from pdu); " +
1023 //                   "END;");
1024 //
1025 //        db.execSQL("CREATE TRIGGER delete_obsolete_threads_when_update_pdu " +
1026 //                   "AFTER UPDATE OF " + Mms.THREAD_ID + " ON pdu " +
1027 //                   "WHEN old." + Mms.THREAD_ID + " != new." + Mms.THREAD_ID + " " +
1028 //                   "BEGIN " +
1029 //                   "  DELETE FROM threads " +
1030 //                   "  WHERE " +
1031 //                   "    _id = old.thread_id " +
1032 //                   "    AND _id NOT IN " +
1033 //                   "    (SELECT thread_id FROM sms " +
1034 //                   "     UNION SELECT thread_id from pdu); " +
1035 //                   "END;");
1036 
1037         // TODO Add triggers for SMS retry-status management.
1038 
1039         // Update the error flag of threads when the error type of
1040         // a pending MM is updated.
1041         db.execSQL("CREATE TRIGGER update_threads_error_on_update_mms " +
1042                    "  AFTER UPDATE OF err_type ON pending_msgs " +
1043                    "  WHEN (OLD.err_type < 10 AND NEW.err_type >= 10)" +
1044                    "    OR (OLD.err_type >= 10 AND NEW.err_type < 10) " +
1045                    "BEGIN" +
1046                    "  UPDATE threads SET error = " +
1047                    "    CASE" +
1048                    "      WHEN NEW.err_type >= 10 THEN error + 1" +
1049                    "      ELSE error - 1" +
1050                    "    END " +
1051                    "  WHERE _id =" +
1052                    "   (SELECT DISTINCT thread_id" +
1053                    "    FROM pdu" +
1054                    "    WHERE _id = NEW.msg_id); " +
1055                    "END;");
1056 
1057         // Update the error flag of threads after a text message was
1058         // failed to send/receive.
1059         db.execSQL("CREATE TRIGGER update_threads_error_on_update_sms " +
1060                    "  AFTER UPDATE OF type ON sms" +
1061                    "  WHEN (OLD.type != 5 AND NEW.type = 5)" +
1062                    "    OR (OLD.type = 5 AND NEW.type != 5) " +
1063                    "BEGIN " +
1064                    "  UPDATE threads SET error = " +
1065                    "    CASE" +
1066                    "      WHEN NEW.type = 5 THEN error + 1" +
1067                    "      ELSE error - 1" +
1068                    "    END " +
1069                    "  WHERE _id = NEW.thread_id; " +
1070                    "END;");
1071     }
1072 
1073     @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)1074     public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
1075         Log.w(TAG, "Upgrading database from version " + oldVersion
1076                 + " to " + currentVersion + ".");
1077 
1078         switch (oldVersion) {
1079         case 40:
1080             if (currentVersion <= 40) {
1081                 return;
1082             }
1083 
1084             db.beginTransaction();
1085             try {
1086                 upgradeDatabaseToVersion41(db);
1087                 db.setTransactionSuccessful();
1088             } catch (Throwable ex) {
1089                 Log.e(TAG, ex.getMessage(), ex);
1090                 break;
1091             } finally {
1092                 db.endTransaction();
1093             }
1094             // fall through
1095         case 41:
1096             if (currentVersion <= 41) {
1097                 return;
1098             }
1099 
1100             db.beginTransaction();
1101             try {
1102                 upgradeDatabaseToVersion42(db);
1103                 db.setTransactionSuccessful();
1104             } catch (Throwable ex) {
1105                 Log.e(TAG, ex.getMessage(), ex);
1106                 break;
1107             } finally {
1108                 db.endTransaction();
1109             }
1110             // fall through
1111         case 42:
1112             if (currentVersion <= 42) {
1113                 return;
1114             }
1115 
1116             db.beginTransaction();
1117             try {
1118                 upgradeDatabaseToVersion43(db);
1119                 db.setTransactionSuccessful();
1120             } catch (Throwable ex) {
1121                 Log.e(TAG, ex.getMessage(), ex);
1122                 break;
1123             } finally {
1124                 db.endTransaction();
1125             }
1126             // fall through
1127         case 43:
1128             if (currentVersion <= 43) {
1129                 return;
1130             }
1131 
1132             db.beginTransaction();
1133             try {
1134                 upgradeDatabaseToVersion44(db);
1135                 db.setTransactionSuccessful();
1136             } catch (Throwable ex) {
1137                 Log.e(TAG, ex.getMessage(), ex);
1138                 break;
1139             } finally {
1140                 db.endTransaction();
1141             }
1142             // fall through
1143         case 44:
1144             if (currentVersion <= 44) {
1145                 return;
1146             }
1147 
1148             db.beginTransaction();
1149             try {
1150                 upgradeDatabaseToVersion45(db);
1151                 db.setTransactionSuccessful();
1152             } catch (Throwable ex) {
1153                 Log.e(TAG, ex.getMessage(), ex);
1154                 break;
1155             } finally {
1156                 db.endTransaction();
1157             }
1158             // fall through
1159         case 45:
1160             if (currentVersion <= 45) {
1161                 return;
1162             }
1163             db.beginTransaction();
1164             try {
1165                 upgradeDatabaseToVersion46(db);
1166                 db.setTransactionSuccessful();
1167             } catch (Throwable ex) {
1168                 Log.e(TAG, ex.getMessage(), ex);
1169                 break;
1170             } finally {
1171                 db.endTransaction();
1172             }
1173             // fall through
1174         case 46:
1175             if (currentVersion <= 46) {
1176                 return;
1177             }
1178 
1179             db.beginTransaction();
1180             try {
1181                 upgradeDatabaseToVersion47(db);
1182                 db.setTransactionSuccessful();
1183             } catch (Throwable ex) {
1184                 Log.e(TAG, ex.getMessage(), ex);
1185                 break;
1186             } finally {
1187                 db.endTransaction();
1188             }
1189             // fall through
1190         case 47:
1191             if (currentVersion <= 47) {
1192                 return;
1193             }
1194 
1195             db.beginTransaction();
1196             try {
1197                 upgradeDatabaseToVersion48(db);
1198                 db.setTransactionSuccessful();
1199             } catch (Throwable ex) {
1200                 Log.e(TAG, ex.getMessage(), ex);
1201                 break;
1202             } finally {
1203                 db.endTransaction();
1204             }
1205             // fall through
1206         case 48:
1207             if (currentVersion <= 48) {
1208                 return;
1209             }
1210 
1211             db.beginTransaction();
1212             try {
1213                 createWordsTables(db);
1214                 db.setTransactionSuccessful();
1215             } catch (Throwable ex) {
1216                 Log.e(TAG, ex.getMessage(), ex);
1217                 break;
1218             } finally {
1219                 db.endTransaction();
1220             }
1221             // fall through
1222         case 49:
1223             if (currentVersion <= 49) {
1224                 return;
1225             }
1226             db.beginTransaction();
1227             try {
1228                 createThreadIdIndex(db);
1229                 db.setTransactionSuccessful();
1230             } catch (Throwable ex) {
1231                 Log.e(TAG, ex.getMessage(), ex);
1232                 break; // force to destroy all old data;
1233             } finally {
1234                 db.endTransaction();
1235             }
1236             // fall through
1237         case 50:
1238             if (currentVersion <= 50) {
1239                 return;
1240             }
1241 
1242             db.beginTransaction();
1243             try {
1244                 upgradeDatabaseToVersion51(db);
1245                 db.setTransactionSuccessful();
1246             } catch (Throwable ex) {
1247                 Log.e(TAG, ex.getMessage(), ex);
1248                 break;
1249             } finally {
1250                 db.endTransaction();
1251             }
1252             // fall through
1253         case 51:
1254             if (currentVersion <= 51) {
1255                 return;
1256             }
1257             // 52 was adding a new meta_data column, but that was removed.
1258             // fall through
1259         case 52:
1260             if (currentVersion <= 52) {
1261                 return;
1262             }
1263 
1264             db.beginTransaction();
1265             try {
1266                 upgradeDatabaseToVersion53(db);
1267                 db.setTransactionSuccessful();
1268             } catch (Throwable ex) {
1269                 Log.e(TAG, ex.getMessage(), ex);
1270                 break;
1271             } finally {
1272                 db.endTransaction();
1273             }
1274             // fall through
1275         case 53:
1276             if (currentVersion <= 53) {
1277                 return;
1278             }
1279 
1280             db.beginTransaction();
1281             try {
1282                 upgradeDatabaseToVersion54(db);
1283                 db.setTransactionSuccessful();
1284             } catch (Throwable ex) {
1285                 Log.e(TAG, ex.getMessage(), ex);
1286                 break;
1287             } finally {
1288                 db.endTransaction();
1289             }
1290             // fall through
1291         case 54:
1292             if (currentVersion <= 54) {
1293                 return;
1294             }
1295 
1296             db.beginTransaction();
1297             try {
1298                 upgradeDatabaseToVersion55(db);
1299                 db.setTransactionSuccessful();
1300             } catch (Throwable ex) {
1301                 Log.e(TAG, ex.getMessage(), ex);
1302                 break;
1303             } finally {
1304                 db.endTransaction();
1305             }
1306             // fall through
1307         case 55:
1308             if (currentVersion <= 55) {
1309                 return;
1310             }
1311 
1312             db.beginTransaction();
1313             try {
1314                 upgradeDatabaseToVersion56(db);
1315                 db.setTransactionSuccessful();
1316             } catch (Throwable ex) {
1317                 Log.e(TAG, ex.getMessage(), ex);
1318                 break;
1319             } finally {
1320                 db.endTransaction();
1321             }
1322             // fall through
1323         case 56:
1324             if (currentVersion <= 56) {
1325                 return;
1326             }
1327 
1328             db.beginTransaction();
1329             try {
1330                 upgradeDatabaseToVersion57(db);
1331                 db.setTransactionSuccessful();
1332             } catch (Throwable ex) {
1333                 Log.e(TAG, ex.getMessage(), ex);
1334                 break;
1335             } finally {
1336                 db.endTransaction();
1337             }
1338             // fall through
1339         case 57:
1340             if (currentVersion <= 57) {
1341                 return;
1342             }
1343 
1344             db.beginTransaction();
1345             try {
1346                 upgradeDatabaseToVersion58(db);
1347                 db.setTransactionSuccessful();
1348             } catch (Throwable ex) {
1349                 Log.e(TAG, ex.getMessage(), ex);
1350                 break;
1351             } finally {
1352                 db.endTransaction();
1353             }
1354             // fall through
1355         case 58:
1356             if (currentVersion <= 58) {
1357                 return;
1358             }
1359 
1360             db.beginTransaction();
1361             try {
1362                 upgradeDatabaseToVersion59(db);
1363                 db.setTransactionSuccessful();
1364             } catch (Throwable ex) {
1365                 Log.e(TAG, ex.getMessage(), ex);
1366                 break;
1367             } finally {
1368                 db.endTransaction();
1369             }
1370             // fall through
1371         case 59:
1372             if (currentVersion <= 59) {
1373                 return;
1374             }
1375 
1376             db.beginTransaction();
1377             try {
1378                 upgradeDatabaseToVersion60(db);
1379                 db.setTransactionSuccessful();
1380             } catch (Throwable ex) {
1381                 Log.e(TAG, ex.getMessage(), ex);
1382                 break;
1383             } finally {
1384                 db.endTransaction();
1385             }
1386             // fall through
1387         case 60:
1388             if (currentVersion <= 60) {
1389                 return;
1390             }
1391 
1392             db.beginTransaction();
1393             try {
1394                 upgradeDatabaseToVersion61(db);
1395                 db.setTransactionSuccessful();
1396             } catch (Throwable ex) {
1397                 Log.e(TAG, ex.getMessage(), ex);
1398                 break;
1399             } finally {
1400                 db.endTransaction();
1401             }
1402             // fall through
1403         case 61:
1404             if (currentVersion <= 61) {
1405                 return;
1406             }
1407 
1408             db.beginTransaction();
1409             try {
1410                 upgradeDatabaseToVersion62(db);
1411                 db.setTransactionSuccessful();
1412             } catch (Throwable ex) {
1413                 Log.e(TAG, ex.getMessage(), ex);
1414                 break;
1415             } finally {
1416                 db.endTransaction();
1417             }
1418             // fall through
1419         case 62:
1420             if (currentVersion <= 62) {
1421                 return;
1422             }
1423 
1424             db.beginTransaction();
1425             try {
1426                 upgradeDatabaseToVersion63(db);
1427                 db.setTransactionSuccessful();
1428             } catch (Throwable ex) {
1429                 Log.e(TAG, ex.getMessage(), ex);
1430                 break;
1431             } finally {
1432                 db.endTransaction();
1433             }
1434             // fall through
1435         case 63:
1436             if (currentVersion <= 63) {
1437                 return;
1438             }
1439 
1440             db.beginTransaction();
1441             try {
1442                 upgradeDatabaseToVersion64(db);
1443                 db.setTransactionSuccessful();
1444             } catch (Throwable ex) {
1445                 Log.e(TAG, ex.getMessage(), ex);
1446                 break;
1447             } finally {
1448                 db.endTransaction();
1449             }
1450 
1451             return;
1452         }
1453 
1454         Log.e(TAG, "Destroying all old data.");
1455         dropAll(db);
1456         onCreate(db);
1457     }
1458 
dropAll(SQLiteDatabase db)1459     private void dropAll(SQLiteDatabase db) {
1460         // Clean the database out in order to start over from scratch.
1461         // We don't need to drop our triggers here because SQLite automatically
1462         // drops a trigger when its attached database is dropped.
1463         db.execSQL("DROP TABLE IF EXISTS canonical_addresses");
1464         db.execSQL("DROP TABLE IF EXISTS threads");
1465         db.execSQL("DROP TABLE IF EXISTS " + MmsSmsProvider.TABLE_PENDING_MSG);
1466         db.execSQL("DROP TABLE IF EXISTS sms");
1467         db.execSQL("DROP TABLE IF EXISTS raw");
1468         db.execSQL("DROP TABLE IF EXISTS attachments");
1469         db.execSQL("DROP TABLE IF EXISTS thread_ids");
1470         db.execSQL("DROP TABLE IF EXISTS sr_pending");
1471         db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PDU + ";");
1472         db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_ADDR + ";");
1473         db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PART + ";");
1474         db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_RATE + ";");
1475         db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_DRM + ";");
1476     }
1477 
upgradeDatabaseToVersion41(SQLiteDatabase db)1478     private void upgradeDatabaseToVersion41(SQLiteDatabase db) {
1479         db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms");
1480         db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " +
1481                    "  BEFORE UPDATE OF msg_box ON pdu " +
1482                    "  WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " +
1483                    "  AND (OLD._id IN (SELECT DISTINCT msg_id" +
1484                    "                   FROM pending_msgs" +
1485                    "                   WHERE err_type >= 10)) " +
1486                    "BEGIN " +
1487                    "  UPDATE threads SET error = error - 1" +
1488                    "  WHERE _id = OLD.thread_id; " +
1489                    "END;");
1490     }
1491 
upgradeDatabaseToVersion42(SQLiteDatabase db)1492     private void upgradeDatabaseToVersion42(SQLiteDatabase db) {
1493         db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete");
1494         db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_sms");
1495         db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_sms");
1496     }
1497 
upgradeDatabaseToVersion43(SQLiteDatabase db)1498     private void upgradeDatabaseToVersion43(SQLiteDatabase db) {
1499         // Add 'has_attachment' column to threads table.
1500         db.execSQL("ALTER TABLE threads ADD COLUMN has_attachment INTEGER DEFAULT 0");
1501 
1502         updateThreadsAttachmentColumn(db);
1503 
1504         // Add insert and delete triggers for keeping it up to date.
1505         db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER);
1506         db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER);
1507     }
1508 
upgradeDatabaseToVersion44(SQLiteDatabase db)1509     private void upgradeDatabaseToVersion44(SQLiteDatabase db) {
1510         updateThreadsAttachmentColumn(db);
1511 
1512         // add the update trigger for keeping the threads up to date.
1513         db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER);
1514     }
1515 
upgradeDatabaseToVersion45(SQLiteDatabase db)1516     private void upgradeDatabaseToVersion45(SQLiteDatabase db) {
1517         // Add 'locked' column to sms table.
1518         db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.LOCKED + " INTEGER DEFAULT 0");
1519 
1520         // Add 'locked' column to pdu table.
1521         db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.LOCKED + " INTEGER DEFAULT 0");
1522     }
1523 
upgradeDatabaseToVersion46(SQLiteDatabase db)1524     private void upgradeDatabaseToVersion46(SQLiteDatabase db) {
1525         // add the "text" column for caching inline text (e.g. strings) instead of
1526         // putting them in an external file
1527         db.execSQL("ALTER TABLE part ADD COLUMN " + Part.TEXT + " TEXT");
1528 
1529         Cursor textRows = db.query(
1530                 "part",
1531                 new String[] { Part._ID, Part._DATA, Part.TEXT},
1532                 "ct = 'text/plain' OR ct == 'application/smil'",
1533                 null,
1534                 null,
1535                 null,
1536                 null);
1537         ArrayList<String> filesToDelete = new ArrayList<String>();
1538         try {
1539             db.beginTransaction();
1540             if (textRows != null) {
1541                 int partDataColumn = textRows.getColumnIndex(Part._DATA);
1542 
1543                 // This code is imperfect in that we can't guarantee that all the
1544                 // backing files get deleted.  For example if the system aborts after
1545                 // the database is updated but before we complete the process of
1546                 // deleting files.
1547                 while (textRows.moveToNext()) {
1548                     String path = textRows.getString(partDataColumn);
1549                     if (path != null) {
1550                         try {
1551                             InputStream is = new FileInputStream(path);
1552                             byte [] data = new byte[is.available()];
1553                             is.read(data);
1554                             EncodedStringValue v = new EncodedStringValue(data);
1555                             db.execSQL("UPDATE part SET " + Part._DATA + " = NULL, " +
1556                                     Part.TEXT + " = ?", new String[] { v.getString() });
1557                             is.close();
1558                             filesToDelete.add(path);
1559                         } catch (IOException e) {
1560                             // TODO Auto-generated catch block
1561                             e.printStackTrace();
1562                         }
1563                     }
1564                 }
1565             }
1566             db.setTransactionSuccessful();
1567         } finally {
1568             db.endTransaction();
1569             for (String pathToDelete : filesToDelete) {
1570                 try {
1571                     (new File(pathToDelete)).delete();
1572                 } catch (SecurityException ex) {
1573                     Log.e(TAG, "unable to clean up old mms file for " + pathToDelete, ex);
1574                 }
1575             }
1576             if (textRows != null) {
1577                 textRows.close();
1578             }
1579         }
1580     }
1581 
upgradeDatabaseToVersion47(SQLiteDatabase db)1582     private void upgradeDatabaseToVersion47(SQLiteDatabase db) {
1583         updateThreadsAttachmentColumn(db);
1584 
1585         // add the update trigger for keeping the threads up to date.
1586         db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER);
1587     }
1588 
upgradeDatabaseToVersion48(SQLiteDatabase db)1589     private void upgradeDatabaseToVersion48(SQLiteDatabase db) {
1590         // Add 'error_code' column to sms table.
1591         db.execSQL("ALTER TABLE sms ADD COLUMN error_code INTEGER DEFAULT 0");
1592     }
1593 
upgradeDatabaseToVersion51(SQLiteDatabase db)1594     private void upgradeDatabaseToVersion51(SQLiteDatabase db) {
1595         db.execSQL("ALTER TABLE sms add COLUMN seen INTEGER DEFAULT 0");
1596         db.execSQL("ALTER TABLE pdu add COLUMN seen INTEGER DEFAULT 0");
1597 
1598         try {
1599             // update the existing sms and pdu tables so the new "seen" column is the same as
1600             // the "read" column for each row.
1601             ContentValues contentValues = new ContentValues();
1602             contentValues.put("seen", 1);
1603             int count = db.update("sms", contentValues, "read=1", null);
1604             Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count +
1605                     " rows in sms table to have READ=1");
1606             count = db.update("pdu", contentValues, "read=1", null);
1607             Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count +
1608                     " rows in pdu table to have READ=1");
1609         } catch (Exception ex) {
1610             Log.e(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51 caught ", ex);
1611         }
1612     }
1613 
upgradeDatabaseToVersion53(SQLiteDatabase db)1614     private void upgradeDatabaseToVersion53(SQLiteDatabase db) {
1615         db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update");
1616 
1617         // Updates threads table whenever a message in pdu is updated.
1618         db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" +
1619                    "  UPDATE OF " + Mms.READ +
1620                    "  ON " + MmsProvider.TABLE_PDU + " " +
1621                    PDU_UPDATE_THREAD_CONSTRAINTS +
1622                    "BEGIN " +
1623                    PDU_UPDATE_THREAD_READ_BODY +
1624                    "END;");
1625     }
1626 
upgradeDatabaseToVersion54(SQLiteDatabase db)1627     private void upgradeDatabaseToVersion54(SQLiteDatabase db) {
1628         // Add 'date_sent' column to sms table.
1629         db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.DATE_SENT + " INTEGER DEFAULT 0");
1630 
1631         // Add 'date_sent' column to pdu table.
1632         db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.DATE_SENT + " INTEGER DEFAULT 0");
1633     }
1634 
upgradeDatabaseToVersion55(SQLiteDatabase db)1635     private void upgradeDatabaseToVersion55(SQLiteDatabase db) {
1636         // Drop removed triggers
1637         db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_pdu");
1638         db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_when_update_pdu");
1639     }
1640 
upgradeDatabaseToVersion56(SQLiteDatabase db)1641     private void upgradeDatabaseToVersion56(SQLiteDatabase db) {
1642         // Add 'text_only' column to pdu table.
1643         db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU + " ADD COLUMN " + Mms.TEXT_ONLY +
1644                 " INTEGER DEFAULT 0");
1645     }
1646 
upgradeDatabaseToVersion57(SQLiteDatabase db)1647     private void upgradeDatabaseToVersion57(SQLiteDatabase db) {
1648         // Clear out bad rows, those with empty threadIds, from the pdu table.
1649         db.execSQL("DELETE FROM " + MmsProvider.TABLE_PDU + " WHERE " + Mms.THREAD_ID + " IS NULL");
1650     }
1651 
upgradeDatabaseToVersion58(SQLiteDatabase db)1652     private void upgradeDatabaseToVersion58(SQLiteDatabase db) {
1653         db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU +
1654                 " ADD COLUMN " + Mms.SUBSCRIPTION_ID
1655                 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1656         db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_PENDING_MSG
1657                 +" ADD COLUMN " + "pending_sub_id"
1658                 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1659         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SMS
1660                 + " ADD COLUMN " + Sms.SUBSCRIPTION_ID
1661                 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1662         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW
1663                 +" ADD COLUMN " + Sms.SUBSCRIPTION_ID
1664                 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1665     }
1666 
upgradeDatabaseToVersion59(SQLiteDatabase db)1667     private void upgradeDatabaseToVersion59(SQLiteDatabase db) {
1668         db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU +" ADD COLUMN "
1669                 + Mms.CREATOR + " TEXT");
1670         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SMS +" ADD COLUMN "
1671                 + Sms.CREATOR + " TEXT");
1672     }
1673 
upgradeDatabaseToVersion60(SQLiteDatabase db)1674     private void upgradeDatabaseToVersion60(SQLiteDatabase db) {
1675         db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_THREADS +" ADD COLUMN "
1676                 + Threads.ARCHIVED + " INTEGER DEFAULT 0");
1677     }
1678 
upgradeDatabaseToVersion61(SQLiteDatabase db)1679     private void upgradeDatabaseToVersion61(SQLiteDatabase db) {
1680         db.execSQL("CREATE VIEW " + SmsProvider.VIEW_SMS_RESTRICTED + " AS " +
1681                    "SELECT * FROM " + SmsProvider.TABLE_SMS + " WHERE " +
1682                    Sms.TYPE + "=" + Sms.MESSAGE_TYPE_INBOX +
1683                    " OR " +
1684                    Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";");
1685         db.execSQL("CREATE VIEW " + MmsProvider.VIEW_PDU_RESTRICTED + "  AS " +
1686                    "SELECT * FROM " + MmsProvider.TABLE_PDU + " WHERE " +
1687                    "(" + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_INBOX +
1688                    " OR " +
1689                    Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_SENT + ")" +
1690                    " AND " +
1691                    "(" + Mms.MESSAGE_TYPE + "!=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + ");");
1692 
1693     }
1694 
upgradeDatabaseToVersion62(SQLiteDatabase db)1695     private void upgradeDatabaseToVersion62(SQLiteDatabase db) {
1696         // When a non-FBE device is upgraded to N, all MMS attachment files are moved from
1697         // /data/data to /data/user_de. We need to update the paths stored in the parts table to
1698         // reflect this change.
1699         String newPartsDirPath;
1700         try {
1701             newPartsDirPath = mContext.getDir(MmsProvider.PARTS_DIR_NAME, 0).getCanonicalPath();
1702         }
1703         catch (IOException e){
1704             Log.e(TAG, "openFile: check file path failed " + e, e);
1705             return;
1706         }
1707 
1708         // The old path of the part files will be something like this:
1709         //   /data/data/0/com.android.providers.telephony/app_parts
1710         // The new path of the part files will be something like this:
1711         //   /data/user_de/0/com.android.providers.telephony/app_parts
1712         int partsDirIndex = newPartsDirPath.lastIndexOf(
1713             File.separator, newPartsDirPath.lastIndexOf(MmsProvider.PARTS_DIR_NAME));
1714         String partsDirName = newPartsDirPath.substring(partsDirIndex) + File.separator;
1715         // The query to update the part path will be:
1716         //   UPDATE part SET _data = '/data/user_de/0/com.android.providers.telephony' ||
1717         //                           SUBSTR(_data, INSTR(_data, '/app_parts/'))
1718         //   WHERE INSTR(_data, '/app_parts/') > 0
1719         db.execSQL("UPDATE " + MmsProvider.TABLE_PART +
1720             " SET " + Part._DATA + " = '" + newPartsDirPath.substring(0, partsDirIndex) + "' ||" +
1721             " SUBSTR(" + Part._DATA + ", INSTR(" + Part._DATA + ", '" + partsDirName + "'))" +
1722             " WHERE INSTR(" + Part._DATA + ", '" + partsDirName + "') > 0");
1723     }
1724 
upgradeDatabaseToVersion63(SQLiteDatabase db)1725     private void upgradeDatabaseToVersion63(SQLiteDatabase db) {
1726         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN deleted INTEGER DEFAULT 0");
1727     }
1728 
upgradeDatabaseToVersion64(SQLiteDatabase db)1729     private void upgradeDatabaseToVersion64(SQLiteDatabase db) {
1730         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN message_body TEXT");
1731     }
1732 
1733     @Override
getWritableDatabase()1734     public synchronized SQLiteDatabase getWritableDatabase() {
1735         SQLiteDatabase db = super.getWritableDatabase();
1736 
1737         if (!sTriedAutoIncrement) {
1738             sTriedAutoIncrement = true;
1739             boolean hasAutoIncrementThreads = hasAutoIncrement(db, MmsSmsProvider.TABLE_THREADS);
1740             boolean hasAutoIncrementAddresses = hasAutoIncrement(db, "canonical_addresses");
1741             boolean hasAutoIncrementPart = hasAutoIncrement(db, "part");
1742             boolean hasAutoIncrementPdu = hasAutoIncrement(db, "pdu");
1743             Log.d(TAG, "[getWritableDatabase] hasAutoIncrementThreads: " + hasAutoIncrementThreads +
1744                     " hasAutoIncrementAddresses: " + hasAutoIncrementAddresses +
1745                     " hasAutoIncrementPart: " + hasAutoIncrementPart +
1746                     " hasAutoIncrementPdu: " + hasAutoIncrementPdu);
1747             boolean autoIncrementThreadsSuccess = true;
1748             boolean autoIncrementAddressesSuccess = true;
1749             boolean autoIncrementPartSuccess = true;
1750             boolean autoIncrementPduSuccess = true;
1751             if (!hasAutoIncrementThreads) {
1752                 db.beginTransaction();
1753                 try {
1754                     if (false && sFakeLowStorageTest) {
1755                         Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " +
1756                                 " - fake exception");
1757                         throw new Exception("FakeLowStorageTest");
1758                     }
1759                     upgradeThreadsTableToAutoIncrement(db);     // a no-op if already upgraded
1760                     db.setTransactionSuccessful();
1761                 } catch (Throwable ex) {
1762                     Log.e(TAG, "Failed to add autoIncrement to threads;: " + ex.getMessage(), ex);
1763                     autoIncrementThreadsSuccess = false;
1764                 } finally {
1765                     db.endTransaction();
1766                 }
1767             }
1768             if (!hasAutoIncrementAddresses) {
1769                 db.beginTransaction();
1770                 try {
1771                     if (false && sFakeLowStorageTest) {
1772                         Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " +
1773                         " - fake exception");
1774                         throw new Exception("FakeLowStorageTest");
1775                     }
1776                     upgradeAddressTableToAutoIncrement(db);     // a no-op if already upgraded
1777                     db.setTransactionSuccessful();
1778                 } catch (Throwable ex) {
1779                     Log.e(TAG, "Failed to add autoIncrement to canonical_addresses: " +
1780                             ex.getMessage(), ex);
1781                     autoIncrementAddressesSuccess = false;
1782                 } finally {
1783                     db.endTransaction();
1784                 }
1785             }
1786             if (!hasAutoIncrementPart) {
1787                 db.beginTransaction();
1788                 try {
1789                     if (false && sFakeLowStorageTest) {
1790                         Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " +
1791                         " - fake exception");
1792                         throw new Exception("FakeLowStorageTest");
1793                     }
1794                     upgradePartTableToAutoIncrement(db);     // a no-op if already upgraded
1795                     db.setTransactionSuccessful();
1796                 } catch (Throwable ex) {
1797                     Log.e(TAG, "Failed to add autoIncrement to part: " +
1798                             ex.getMessage(), ex);
1799                     autoIncrementPartSuccess = false;
1800                 } finally {
1801                     db.endTransaction();
1802                 }
1803             }
1804             if (!hasAutoIncrementPdu) {
1805                 db.beginTransaction();
1806                 try {
1807                     if (false && sFakeLowStorageTest) {
1808                         Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " +
1809                         " - fake exception");
1810                         throw new Exception("FakeLowStorageTest");
1811                     }
1812                     upgradePduTableToAutoIncrement(db);     // a no-op if already upgraded
1813                     db.setTransactionSuccessful();
1814                 } catch (Throwable ex) {
1815                     Log.e(TAG, "Failed to add autoIncrement to pdu: " +
1816                             ex.getMessage(), ex);
1817                     autoIncrementPduSuccess = false;
1818                 } finally {
1819                     db.endTransaction();
1820                 }
1821             }
1822             if (autoIncrementThreadsSuccess &&
1823                     autoIncrementAddressesSuccess &&
1824                     autoIncrementPartSuccess &&
1825                     autoIncrementPduSuccess) {
1826                 if (mLowStorageMonitor != null) {
1827                     // We've already updated the database. This receiver is no longer necessary.
1828                     Log.d(TAG, "Unregistering mLowStorageMonitor - we've upgraded");
1829                     mContext.unregisterReceiver(mLowStorageMonitor);
1830                     mLowStorageMonitor = null;
1831                 }
1832             } else {
1833                 if (sFakeLowStorageTest) {
1834                     sFakeLowStorageTest = false;
1835                 }
1836 
1837                 // We failed, perhaps because of low storage. Turn on a receiver to watch for
1838                 // storage space.
1839                 if (mLowStorageMonitor == null) {
1840                     Log.d(TAG, "[getWritableDatabase] turning on storage monitor");
1841                     mLowStorageMonitor = new LowStorageMonitor();
1842                     IntentFilter intentFilter = new IntentFilter();
1843                     intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
1844                     intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
1845                     mContext.registerReceiver(mLowStorageMonitor, intentFilter);
1846                 }
1847             }
1848         }
1849         return db;
1850     }
1851 
1852     // Determine whether a particular table has AUTOINCREMENT in its schema.
hasAutoIncrement(SQLiteDatabase db, String tableName)1853     private boolean hasAutoIncrement(SQLiteDatabase db, String tableName) {
1854         boolean result = false;
1855         String query = "SELECT sql FROM sqlite_master WHERE type='table' AND name='" +
1856                         tableName + "'";
1857         Cursor c = db.rawQuery(query, null);
1858         if (c != null) {
1859             try {
1860                 if (c.moveToFirst()) {
1861                     String schema = c.getString(0);
1862                     result = schema != null ? schema.contains("AUTOINCREMENT") : false;
1863                     Log.d(TAG, "[MmsSmsDb] tableName: " + tableName + " hasAutoIncrement: " +
1864                             schema + " result: " + result);
1865                 }
1866             } finally {
1867                 c.close();
1868             }
1869         }
1870         return result;
1871     }
1872 
1873     // upgradeThreadsTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to
1874     // the threads table. This could fail if the user has a lot of conversations and not enough
1875     // storage to make a copy of the threads table. That's ok. This upgrade is optional. It'll
1876     // be called again next time the device is rebooted.
upgradeThreadsTableToAutoIncrement(SQLiteDatabase db)1877     private void upgradeThreadsTableToAutoIncrement(SQLiteDatabase db) {
1878         if (hasAutoIncrement(db, MmsSmsProvider.TABLE_THREADS)) {
1879             Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: already upgraded");
1880             return;
1881         }
1882         Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: upgrading");
1883 
1884         // Make the _id of the threads table autoincrement so we never re-use thread ids
1885         // Have to create a new temp threads table. Copy all the info from the old table.
1886         // Drop the old table and rename the new table to that of the old.
1887         db.execSQL("CREATE TABLE threads_temp (" +
1888                 Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1889                 Threads.DATE + " INTEGER DEFAULT 0," +
1890                 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," +
1891                 Threads.RECIPIENT_IDS + " TEXT," +
1892                 Threads.SNIPPET + " TEXT," +
1893                 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," +
1894                 Threads.READ + " INTEGER DEFAULT 1," +
1895                 Threads.TYPE + " INTEGER DEFAULT 0," +
1896                 Threads.ERROR + " INTEGER DEFAULT 0," +
1897                 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);");
1898 
1899         db.execSQL("INSERT INTO threads_temp SELECT * from threads;");
1900         db.execSQL("DROP TABLE threads;");
1901         db.execSQL("ALTER TABLE threads_temp RENAME TO threads;");
1902     }
1903 
1904     // upgradeAddressTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to
1905     // the canonical_addresses table. This could fail if the user has a lot of people they've
1906     // messaged with and not enough storage to make a copy of the canonical_addresses table.
1907     // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted.
upgradeAddressTableToAutoIncrement(SQLiteDatabase db)1908     private void upgradeAddressTableToAutoIncrement(SQLiteDatabase db) {
1909         if (hasAutoIncrement(db, "canonical_addresses")) {
1910             Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: already upgraded");
1911             return;
1912         }
1913         Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: upgrading");
1914 
1915         // Make the _id of the canonical_addresses table autoincrement so we never re-use ids
1916         // Have to create a new temp canonical_addresses table. Copy all the info from the old
1917         // table. Drop the old table and rename the new table to that of the old.
1918         db.execSQL("CREATE TABLE canonical_addresses_temp (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
1919                 "address TEXT);");
1920 
1921         db.execSQL("INSERT INTO canonical_addresses_temp SELECT * from canonical_addresses;");
1922         db.execSQL("DROP TABLE canonical_addresses;");
1923         db.execSQL("ALTER TABLE canonical_addresses_temp RENAME TO canonical_addresses;");
1924     }
1925 
1926     // upgradePartTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to
1927     // the part table. This could fail if the user has a lot of sound/video/picture attachments
1928     // and not enough storage to make a copy of the part table.
1929     // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted.
upgradePartTableToAutoIncrement(SQLiteDatabase db)1930     private void upgradePartTableToAutoIncrement(SQLiteDatabase db) {
1931         if (hasAutoIncrement(db, "part")) {
1932             Log.d(TAG, "[MmsSmsDb] upgradePartTableToAutoIncrement: already upgraded");
1933             return;
1934         }
1935         Log.d(TAG, "[MmsSmsDb] upgradePartTableToAutoIncrement: upgrading");
1936 
1937         // Make the _id of the part table autoincrement so we never re-use ids
1938         // Have to create a new temp part table. Copy all the info from the old
1939         // table. Drop the old table and rename the new table to that of the old.
1940         db.execSQL("CREATE TABLE part_temp (" +
1941                 Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1942                 Part.MSG_ID + " INTEGER," +
1943                 Part.SEQ + " INTEGER DEFAULT 0," +
1944                 Part.CONTENT_TYPE + " TEXT," +
1945                 Part.NAME + " TEXT," +
1946                 Part.CHARSET + " INTEGER," +
1947                 Part.CONTENT_DISPOSITION + " TEXT," +
1948                 Part.FILENAME + " TEXT," +
1949                 Part.CONTENT_ID + " TEXT," +
1950                 Part.CONTENT_LOCATION + " TEXT," +
1951                 Part.CT_START + " INTEGER," +
1952                 Part.CT_TYPE + " TEXT," +
1953                 Part._DATA + " TEXT," +
1954                 Part.TEXT + " TEXT);");
1955 
1956         db.execSQL("INSERT INTO part_temp SELECT * from part;");
1957         db.execSQL("DROP TABLE part;");
1958         db.execSQL("ALTER TABLE part_temp RENAME TO part;");
1959 
1960         // part-related triggers get tossed when the part table is dropped -- rebuild them.
1961         createMmsTriggers(db);
1962     }
1963 
1964     // upgradePduTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to
1965     // the pdu table. This could fail if the user has a lot of mms messages
1966     // and not enough storage to make a copy of the pdu table.
1967     // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted.
upgradePduTableToAutoIncrement(SQLiteDatabase db)1968     private void upgradePduTableToAutoIncrement(SQLiteDatabase db) {
1969         if (hasAutoIncrement(db, "pdu")) {
1970             Log.d(TAG, "[MmsSmsDb] upgradePduTableToAutoIncrement: already upgraded");
1971             return;
1972         }
1973         Log.d(TAG, "[MmsSmsDb] upgradePduTableToAutoIncrement: upgrading");
1974 
1975         // Make the _id of the part table autoincrement so we never re-use ids
1976         // Have to create a new temp part table. Copy all the info from the old
1977         // table. Drop the old table and rename the new table to that of the old.
1978         db.execSQL("CREATE TABLE pdu_temp (" +
1979                 Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1980                 Mms.THREAD_ID + " INTEGER," +
1981                 Mms.DATE + " INTEGER," +
1982                 Mms.DATE_SENT + " INTEGER DEFAULT 0," +
1983                 Mms.MESSAGE_BOX + " INTEGER," +
1984                 Mms.READ + " INTEGER DEFAULT 0," +
1985                 Mms.MESSAGE_ID + " TEXT," +
1986                 Mms.SUBJECT + " TEXT," +
1987                 Mms.SUBJECT_CHARSET + " INTEGER," +
1988                 Mms.CONTENT_TYPE + " TEXT," +
1989                 Mms.CONTENT_LOCATION + " TEXT," +
1990                 Mms.EXPIRY + " INTEGER," +
1991                 Mms.MESSAGE_CLASS + " TEXT," +
1992                 Mms.MESSAGE_TYPE + " INTEGER," +
1993                 Mms.MMS_VERSION + " INTEGER," +
1994                 Mms.MESSAGE_SIZE + " INTEGER," +
1995                 Mms.PRIORITY + " INTEGER," +
1996                 Mms.READ_REPORT + " INTEGER," +
1997                 Mms.REPORT_ALLOWED + " INTEGER," +
1998                 Mms.RESPONSE_STATUS + " INTEGER," +
1999                 Mms.STATUS + " INTEGER," +
2000                 Mms.TRANSACTION_ID + " TEXT," +
2001                 Mms.RETRIEVE_STATUS + " INTEGER," +
2002                 Mms.RETRIEVE_TEXT + " TEXT," +
2003                 Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," +
2004                 Mms.READ_STATUS + " INTEGER," +
2005                 Mms.CONTENT_CLASS + " INTEGER," +
2006                 Mms.RESPONSE_TEXT + " TEXT," +
2007                 Mms.DELIVERY_TIME + " INTEGER," +
2008                 Mms.DELIVERY_REPORT + " INTEGER," +
2009                 Mms.LOCKED + " INTEGER DEFAULT 0," +
2010                 Mms.SUBSCRIPTION_ID + " INTEGER DEFAULT "
2011                         + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
2012                 Mms.SEEN + " INTEGER DEFAULT 0," +
2013                 Mms.TEXT_ONLY + " INTEGER DEFAULT 0" +
2014                 ");");
2015 
2016         db.execSQL("INSERT INTO pdu_temp SELECT * from pdu;");
2017         db.execSQL("DROP TABLE pdu;");
2018         db.execSQL("ALTER TABLE pdu_temp RENAME TO pdu;");
2019 
2020         // pdu-related triggers get tossed when the part table is dropped -- rebuild them.
2021         createMmsTriggers(db);
2022     }
2023 
2024     private class LowStorageMonitor extends BroadcastReceiver {
2025 
LowStorageMonitor()2026         public LowStorageMonitor() {
2027         }
2028 
onReceive(Context context, Intent intent)2029         public void onReceive(Context context, Intent intent) {
2030             String action = intent.getAction();
2031 
2032             Log.d(TAG, "[LowStorageMonitor] onReceive intent " + action);
2033 
2034             if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
2035                 sTriedAutoIncrement = false;    // try to upgrade on the next getWriteableDatabase
2036             }
2037         }
2038     }
2039 
updateThreadsAttachmentColumn(SQLiteDatabase db)2040     private void updateThreadsAttachmentColumn(SQLiteDatabase db) {
2041         // Set the values of that column correctly based on the current
2042         // contents of the database.
2043         db.execSQL("UPDATE threads SET has_attachment=1 WHERE _id IN " +
2044                    "  (SELECT DISTINCT pdu.thread_id FROM part " +
2045                    "   JOIN pdu ON pdu._id=part.mid " +
2046                    "   WHERE part.ct != 'text/plain' AND part.ct != 'application/smil')");
2047     }
2048 }
2049