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