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