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