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