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