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