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