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