2012-09-30 19:56:29 -07:00
|
|
|
/**
|
2011-12-20 10:20:44 -08:00
|
|
|
* Copyright (C) 2011 Whisper Systems
|
2012-09-30 19:56:29 -07:00
|
|
|
*
|
2011-12-20 10:20:44 -08:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
2012-09-30 19:56:29 -07:00
|
|
|
*
|
2011-12-20 10:20:44 -08:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
package org.thoughtcrime.securesms.database;
|
|
|
|
|
2014-07-18 20:29:00 -07:00
|
|
|
import android.content.ContentValues;
|
2011-12-20 10:20:44 -08:00
|
|
|
import android.content.Context;
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
import android.database.Cursor;
|
2017-08-01 14:49:16 -07:00
|
|
|
import android.database.sqlite.SQLiteConstraintException;
|
2011-12-20 10:20:44 -08:00
|
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
|
|
import android.database.sqlite.SQLiteDatabase.CursorFactory;
|
2012-09-30 19:56:29 -07:00
|
|
|
import android.database.sqlite.SQLiteOpenHelper;
|
2017-07-26 09:59:15 -07:00
|
|
|
import android.support.annotation.Nullable;
|
2014-11-12 11:15:05 -08:00
|
|
|
import android.text.TextUtils;
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
import android.util.Log;
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2017-07-26 09:59:15 -07:00
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
|
|
import com.google.i18n.phonenumbers.NumberParseException;
|
|
|
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
|
|
|
import com.google.i18n.phonenumbers.Phonenumber;
|
|
|
|
import com.google.i18n.phonenumbers.ShortNumberInfo;
|
|
|
|
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
2015-07-14 14:31:03 -07:00
|
|
|
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
|
2013-04-26 11:23:43 -07:00
|
|
|
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
2014-11-03 15:16:04 -08:00
|
|
|
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
|
|
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
2014-07-18 20:29:00 -07:00
|
|
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
2014-04-21 08:40:19 -07:00
|
|
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
2014-11-12 11:15:05 -08:00
|
|
|
import org.thoughtcrime.securesms.util.Base64;
|
2017-08-02 11:08:38 -07:00
|
|
|
import org.thoughtcrime.securesms.util.DelimiterUtil;
|
2017-08-01 08:56:00 -07:00
|
|
|
import org.thoughtcrime.securesms.util.Hex;
|
2017-07-26 09:59:15 -07:00
|
|
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
2017-05-08 15:32:59 -07:00
|
|
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
2017-07-26 09:59:15 -07:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
2014-11-12 11:15:05 -08:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2016-03-23 10:34:41 -07:00
|
|
|
import org.whispersystems.libsignal.IdentityKey;
|
|
|
|
import org.whispersystems.libsignal.InvalidMessageException;
|
2013-04-26 11:23:43 -07:00
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.IOException;
|
2014-12-12 01:03:24 -08:00
|
|
|
import java.io.InputStream;
|
2017-08-01 08:56:00 -07:00
|
|
|
import java.security.SecureRandom;
|
2017-08-02 11:08:38 -07:00
|
|
|
import java.util.Arrays;
|
2017-07-26 09:59:15 -07:00
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Set;
|
|
|
|
import java.util.regex.Pattern;
|
2013-04-26 11:23:43 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public class DatabaseFactory {
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2015-10-16 13:59:40 -07:00
|
|
|
private static final int INTRODUCED_IDENTITIES_VERSION = 2;
|
|
|
|
private static final int INTRODUCED_INDEXES_VERSION = 3;
|
|
|
|
private static final int INTRODUCED_DATE_SENT_VERSION = 4;
|
|
|
|
private static final int INTRODUCED_DRAFTS_VERSION = 5;
|
|
|
|
private static final int INTRODUCED_NEW_TYPES_VERSION = 6;
|
|
|
|
private static final int INTRODUCED_MMS_BODY_VERSION = 7;
|
|
|
|
private static final int INTRODUCED_MMS_FROM_VERSION = 8;
|
|
|
|
private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9;
|
|
|
|
private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10;
|
|
|
|
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
|
|
|
|
private static final int INTRODUCED_PUSH_FIX_VERSION = 12;
|
|
|
|
private static final int INTRODUCED_DELIVERY_RECEIPTS = 13;
|
|
|
|
private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14;
|
|
|
|
private static final int INTRODUCED_THUMBNAILS_VERSION = 15;
|
|
|
|
private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16;
|
|
|
|
private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17;
|
|
|
|
private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18;
|
|
|
|
private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19;
|
|
|
|
private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20;
|
|
|
|
private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21;
|
|
|
|
private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22;
|
|
|
|
private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23;
|
2015-11-23 15:07:41 -08:00
|
|
|
private static final int INTRODUCED_ARCHIVE_VERSION = 24;
|
2015-11-24 16:06:41 +01:00
|
|
|
private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25;
|
2015-12-04 22:47:33 +01:00
|
|
|
private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26;
|
2016-02-05 16:10:33 -08:00
|
|
|
private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 27;
|
2016-08-15 20:23:56 -07:00
|
|
|
private static final int INTRODUCED_EXPIRE_MESSAGES_VERSION = 28;
|
2017-02-13 22:35:47 -08:00
|
|
|
private static final int INTRODUCED_LAST_SEEN = 29;
|
2017-02-26 10:06:27 -08:00
|
|
|
private static final int INTRODUCED_DIGEST = 30;
|
2017-03-08 17:38:55 -08:00
|
|
|
private static final int INTRODUCED_NOTIFIED = 31;
|
2017-03-28 12:05:30 -07:00
|
|
|
private static final int INTRODUCED_DOCUMENTS = 32;
|
2017-04-22 16:29:26 -07:00
|
|
|
private static final int INTRODUCED_FAST_PREFLIGHT = 33;
|
2017-05-11 22:46:35 -07:00
|
|
|
private static final int INTRODUCED_VOICE_NOTES = 34;
|
2017-05-19 18:01:40 -07:00
|
|
|
private static final int INTRODUCED_IDENTITY_TIMESTAMP = 35;
|
2017-07-21 15:59:27 -07:00
|
|
|
private static final int SANIFY_ATTACHMENT_DOWNLOAD = 36;
|
2017-07-26 09:59:15 -07:00
|
|
|
private static final int NO_MORE_CANONICAL_ADDRESS_DATABASE = 37;
|
2017-08-01 08:56:00 -07:00
|
|
|
private static final int NO_MORE_RECIPIENTS_PLURAL = 38;
|
2017-08-07 14:24:53 -07:00
|
|
|
private static final int INTERNAL_DIRECTORY = 39;
|
2017-08-07 15:31:12 -07:00
|
|
|
private static final int INTERNAL_SYSTEM_DISPLAY_NAME = 40;
|
2017-08-14 18:11:13 -07:00
|
|
|
private static final int PROFILES = 41;
|
2017-08-16 21:49:41 -07:00
|
|
|
private static final int PROFILE_SHARING_APPROVAL = 42;
|
2017-08-18 17:28:56 -07:00
|
|
|
private static final int UNSEEN_NUMBER_OFFER = 43;
|
|
|
|
private static final int DATABASE_VERSION = 43;
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
private static final String DATABASE_NAME = "messages.db";
|
|
|
|
private static final Object lock = new Object();
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
private static DatabaseFactory instance;
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2013-06-24 21:02:30 -07:00
|
|
|
private DatabaseHelper databaseHelper;
|
2011-12-20 10:20:44 -08:00
|
|
|
|
|
|
|
private final SmsDatabase sms;
|
|
|
|
private final EncryptingSmsDatabase encryptingSms;
|
|
|
|
private final MmsDatabase mms;
|
2015-10-12 18:25:05 -07:00
|
|
|
private final AttachmentDatabase attachments;
|
2017-02-01 03:49:19 +01:00
|
|
|
private final MediaDatabase media;
|
2011-12-20 10:20:44 -08:00
|
|
|
private final ThreadDatabase thread;
|
|
|
|
private final MmsSmsDatabase mmsSmsDatabase;
|
|
|
|
private final IdentityDatabase identityDatabase;
|
2013-02-04 00:13:07 -08:00
|
|
|
private final DraftDatabase draftDatabase;
|
2013-09-08 18:19:05 -07:00
|
|
|
private final PushDatabase pushDatabase;
|
2014-01-14 00:26:43 -08:00
|
|
|
private final GroupDatabase groupDatabase;
|
2017-08-21 18:37:39 -07:00
|
|
|
private final RecipientDatabase recipientDatabase;
|
2015-07-14 14:31:03 -07:00
|
|
|
private final ContactsDatabase contactsDatabase;
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public static DatabaseFactory getInstance(Context context) {
|
|
|
|
synchronized (lock) {
|
|
|
|
if (instance == null)
|
2015-05-21 02:32:38 -07:00
|
|
|
instance = new DatabaseFactory(context.getApplicationContext());
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public static MmsSmsDatabase getMmsSmsDatabase(Context context) {
|
|
|
|
return getInstance(context).mmsSmsDatabase;
|
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public static ThreadDatabase getThreadDatabase(Context context) {
|
|
|
|
return getInstance(context).thread;
|
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public static SmsDatabase getSmsDatabase(Context context) {
|
|
|
|
return getInstance(context).sms;
|
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public static MmsDatabase getMmsDatabase(Context context) {
|
|
|
|
return getInstance(context).mms;
|
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public static EncryptingSmsDatabase getEncryptingSmsDatabase(Context context) {
|
|
|
|
return getInstance(context).encryptingSms;
|
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2015-10-12 18:25:05 -07:00
|
|
|
public static AttachmentDatabase getAttachmentDatabase(Context context) {
|
|
|
|
return getInstance(context).attachments;
|
|
|
|
}
|
|
|
|
|
2017-02-01 03:49:19 +01:00
|
|
|
public static MediaDatabase getMediaDatabase(Context context) {
|
|
|
|
return getInstance(context).media;
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public static IdentityDatabase getIdentityDatabase(Context context) {
|
|
|
|
return getInstance(context).identityDatabase;
|
|
|
|
}
|
|
|
|
|
2013-02-04 00:13:07 -08:00
|
|
|
public static DraftDatabase getDraftDatabase(Context context) {
|
|
|
|
return getInstance(context).draftDatabase;
|
|
|
|
}
|
|
|
|
|
2013-09-08 18:19:05 -07:00
|
|
|
public static PushDatabase getPushDatabase(Context context) {
|
|
|
|
return getInstance(context).pushDatabase;
|
|
|
|
}
|
|
|
|
|
2014-01-14 00:26:43 -08:00
|
|
|
public static GroupDatabase getGroupDatabase(Context context) {
|
|
|
|
return getInstance(context).groupDatabase;
|
|
|
|
}
|
|
|
|
|
2017-08-21 18:47:37 -07:00
|
|
|
public static RecipientDatabase getRecipientDatabase(Context context) {
|
2017-08-21 18:37:39 -07:00
|
|
|
return getInstance(context).recipientDatabase;
|
2015-06-09 07:37:20 -07:00
|
|
|
}
|
|
|
|
|
2015-07-14 14:31:03 -07:00
|
|
|
public static ContactsDatabase getContactsDatabase(Context context) {
|
|
|
|
return getInstance(context).contactsDatabase;
|
|
|
|
}
|
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
private DatabaseFactory(Context context) {
|
2017-08-22 10:44:04 -07:00
|
|
|
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
|
|
|
this.sms = new SmsDatabase(context, databaseHelper);
|
|
|
|
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
|
|
|
|
this.mms = new MmsDatabase(context, databaseHelper);
|
|
|
|
this.attachments = new AttachmentDatabase(context, databaseHelper);
|
|
|
|
this.media = new MediaDatabase(context, databaseHelper);
|
|
|
|
this.thread = new ThreadDatabase(context, databaseHelper);
|
|
|
|
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
|
|
|
|
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
|
|
|
|
this.draftDatabase = new DraftDatabase(context, databaseHelper);
|
|
|
|
this.pushDatabase = new PushDatabase(context, databaseHelper);
|
|
|
|
this.groupDatabase = new GroupDatabase(context, databaseHelper);
|
2017-08-21 18:37:39 -07:00
|
|
|
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
|
2017-08-22 10:44:04 -07:00
|
|
|
this.contactsDatabase = new ContactsDatabase(context);
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2013-06-24 21:02:30 -07:00
|
|
|
public void reset(Context context) {
|
|
|
|
DatabaseHelper old = this.databaseHelper;
|
|
|
|
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
|
|
|
|
|
|
|
this.sms.reset(databaseHelper);
|
|
|
|
this.encryptingSms.reset(databaseHelper);
|
|
|
|
this.mms.reset(databaseHelper);
|
2015-10-12 18:25:05 -07:00
|
|
|
this.attachments.reset(databaseHelper);
|
2013-06-24 21:02:30 -07:00
|
|
|
this.thread.reset(databaseHelper);
|
|
|
|
this.mmsSmsDatabase.reset(databaseHelper);
|
|
|
|
this.identityDatabase.reset(databaseHelper);
|
|
|
|
this.draftDatabase.reset(databaseHelper);
|
2014-01-14 00:26:43 -08:00
|
|
|
this.pushDatabase.reset(databaseHelper);
|
|
|
|
this.groupDatabase.reset(databaseHelper);
|
2017-08-21 18:37:39 -07:00
|
|
|
this.recipientDatabase.reset(databaseHelper);
|
2013-06-24 21:02:30 -07:00
|
|
|
old.close();
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|
2012-09-30 19:56:29 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
public void onApplicationLevelUpgrade(Context context, MasterSecret masterSecret, int fromVersion,
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
DatabaseUpgradeActivity.DatabaseUpgradeListener listener)
|
|
|
|
{
|
2013-05-16 13:16:42 -07:00
|
|
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
|
|
|
db.beginTransaction();
|
|
|
|
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
if (fromVersion < DatabaseUpgradeActivity.NO_MORE_KEY_EXCHANGE_PREFIX_VERSION) {
|
|
|
|
String KEY_EXCHANGE = "?TextSecureKeyExchange";
|
|
|
|
String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd";
|
|
|
|
String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs";
|
2013-05-16 13:16:42 -07:00
|
|
|
int ROW_LIMIT = 500;
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
|
|
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
2013-05-16 13:16:42 -07:00
|
|
|
int smsCount = 0;
|
|
|
|
int threadCount = 0;
|
|
|
|
int skip = 0;
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
Cursor cursor = db.query("sms", new String[] {"COUNT(*)"}, "type & " + 0x80000000 + " != 0",
|
|
|
|
null, null, null, null);
|
2013-04-26 11:23:43 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
smsCount = cursor.getInt(0);
|
|
|
|
cursor.close();
|
|
|
|
}
|
2013-04-26 11:23:43 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
cursor = db.query("thread", new String[] {"COUNT(*)"}, "snippet_type & " + 0x80000000 + " != 0",
|
|
|
|
null, null, null, null);
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
threadCount = cursor.getInt(0);
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
Cursor smsCursor = null;
|
|
|
|
|
|
|
|
Log.w("DatabaseFactory", "Upgrade count: " + (smsCount + threadCount));
|
|
|
|
|
|
|
|
do {
|
|
|
|
Log.w("DatabaseFactory", "Looping SMS cursor...");
|
|
|
|
if (smsCursor != null)
|
|
|
|
smsCursor.close();
|
|
|
|
|
|
|
|
smsCursor = db.query("sms", new String[] {"_id", "type", "body"},
|
|
|
|
"type & " + 0x80000000 + " != 0",
|
|
|
|
null, null, null, "_id", skip + "," + ROW_LIMIT);
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
while (smsCursor != null && smsCursor.moveToNext()) {
|
|
|
|
listener.setProgress(smsCursor.getPosition() + skip, smsCount + threadCount);
|
|
|
|
|
|
|
|
try {
|
|
|
|
String body = masterCipher.decryptBody(smsCursor.getString(smsCursor.getColumnIndexOrThrow("body")));
|
|
|
|
long type = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("type"));
|
|
|
|
long id = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("_id"));
|
|
|
|
|
|
|
|
if (body.startsWith(KEY_EXCHANGE)) {
|
|
|
|
body = body.substring(KEY_EXCHANGE.length());
|
|
|
|
body = masterCipher.encryptBody(body);
|
|
|
|
type |= 0x8000;
|
|
|
|
|
|
|
|
db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
|
|
|
|
new String[] {body, type+"", id+""});
|
|
|
|
} else if (body.startsWith(PROCESSED_KEY_EXCHANGE)) {
|
|
|
|
body = body.substring(PROCESSED_KEY_EXCHANGE.length());
|
|
|
|
body = masterCipher.encryptBody(body);
|
|
|
|
type |= (0x8000 | 0x2000);
|
|
|
|
|
|
|
|
db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
|
|
|
|
new String[] {body, type+"", id+""});
|
|
|
|
} else if (body.startsWith(STALE_KEY_EXCHANGE)) {
|
|
|
|
body = body.substring(STALE_KEY_EXCHANGE.length());
|
|
|
|
body = masterCipher.encryptBody(body);
|
|
|
|
type |= (0x8000 | 0x4000);
|
|
|
|
|
|
|
|
db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
|
|
|
|
new String[] {body, type+"", id+""});
|
|
|
|
}
|
|
|
|
} catch (InvalidMessageException e) {
|
|
|
|
Log.w("DatabaseFactory", e);
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
skip += ROW_LIMIT;
|
|
|
|
} while (smsCursor != null && smsCursor.getCount() > 0);
|
2013-04-26 11:23:43 -07:00
|
|
|
|
|
|
|
|
2013-05-05 12:51:36 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
Cursor threadCursor = null;
|
|
|
|
skip = 0;
|
|
|
|
|
|
|
|
do {
|
|
|
|
Log.w("DatabaseFactory", "Looping thread cursor...");
|
|
|
|
|
|
|
|
if (threadCursor != null)
|
|
|
|
threadCursor.close();
|
|
|
|
|
|
|
|
threadCursor = db.query("thread", new String[] {"_id", "snippet_type", "snippet"},
|
|
|
|
"snippet_type & " + 0x80000000 + " != 0",
|
|
|
|
null, null, null, "_id", skip + "," + ROW_LIMIT);
|
|
|
|
|
|
|
|
while (threadCursor != null && threadCursor.moveToNext()) {
|
|
|
|
listener.setProgress(smsCount + threadCursor.getPosition(), smsCount + threadCount);
|
|
|
|
|
|
|
|
try {
|
|
|
|
String snippet = threadCursor.getString(threadCursor.getColumnIndexOrThrow("snippet"));
|
|
|
|
long snippetType = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("snippet_type"));
|
|
|
|
long id = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id"));
|
|
|
|
|
2014-11-12 11:15:05 -08:00
|
|
|
if (!TextUtils.isEmpty(snippet)) {
|
2013-05-16 13:16:42 -07:00
|
|
|
snippet = masterCipher.decryptBody(snippet);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (snippet.startsWith(KEY_EXCHANGE)) {
|
|
|
|
snippet = snippet.substring(KEY_EXCHANGE.length());
|
|
|
|
snippet = masterCipher.encryptBody(snippet);
|
|
|
|
snippetType |= 0x8000;
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
|
|
|
|
new String[] {snippet, snippetType+"", id+""});
|
|
|
|
} else if (snippet.startsWith(PROCESSED_KEY_EXCHANGE)) {
|
|
|
|
snippet = snippet.substring(PROCESSED_KEY_EXCHANGE.length());
|
|
|
|
snippet = masterCipher.encryptBody(snippet);
|
|
|
|
snippetType |= (0x8000 | 0x2000);
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
|
|
|
|
new String[] {snippet, snippetType+"", id+""});
|
|
|
|
} else if (snippet.startsWith(STALE_KEY_EXCHANGE)) {
|
|
|
|
snippet = snippet.substring(STALE_KEY_EXCHANGE.length());
|
|
|
|
snippet = masterCipher.encryptBody(snippet);
|
|
|
|
snippetType |= (0x8000 | 0x4000);
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
|
|
|
|
new String[] {snippet, snippetType+"", id+""});
|
|
|
|
}
|
|
|
|
} catch (InvalidMessageException e) {
|
|
|
|
Log.w("DatabaseFactory", e);
|
2013-04-26 11:23:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
skip += ROW_LIMIT;
|
|
|
|
} while (threadCursor != null && threadCursor.getCount() > 0);
|
2013-04-26 11:23:43 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
if (smsCursor != null)
|
|
|
|
smsCursor.close();
|
|
|
|
|
|
|
|
if (threadCursor != null)
|
|
|
|
threadCursor.close();
|
2013-04-26 11:23:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fromVersion < DatabaseUpgradeActivity.MMS_BODY_VERSION) {
|
|
|
|
Log.w("DatabaseFactory", "Update MMS bodies...");
|
|
|
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
2013-05-16 13:16:42 -07:00
|
|
|
Cursor mmsCursor = db.query("mms", new String[] {"_id"},
|
|
|
|
"msg_box & " + 0x80000000L + " != 0",
|
|
|
|
null, null, null, null);
|
2013-04-26 11:23:43 -07:00
|
|
|
|
|
|
|
Log.w("DatabaseFactory", "Got MMS rows: " + (mmsCursor == null ? "null" : mmsCursor.getCount()));
|
|
|
|
|
|
|
|
while (mmsCursor != null && mmsCursor.moveToNext()) {
|
|
|
|
listener.setProgress(mmsCursor.getPosition(), mmsCursor.getCount());
|
|
|
|
|
|
|
|
long mmsId = mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow("_id"));
|
|
|
|
String body = null;
|
|
|
|
int partCount = 0;
|
|
|
|
Cursor partCursor = db.query("part", new String[] {"_id", "ct", "_data", "encrypted"},
|
|
|
|
"mid = ?", new String[] {mmsId+""}, null, null, null);
|
|
|
|
|
|
|
|
while (partCursor != null && partCursor.moveToNext()) {
|
|
|
|
String contentType = partCursor.getString(partCursor.getColumnIndexOrThrow("ct"));
|
|
|
|
|
2017-05-08 15:32:59 -07:00
|
|
|
if (MediaUtil.isTextType(contentType)) {
|
2013-04-26 11:23:43 -07:00
|
|
|
try {
|
|
|
|
long partId = partCursor.getLong(partCursor.getColumnIndexOrThrow("_id"));
|
|
|
|
String dataLocation = partCursor.getString(partCursor.getColumnIndexOrThrow("_data"));
|
|
|
|
boolean encrypted = partCursor.getInt(partCursor.getColumnIndexOrThrow("encrypted")) == 1;
|
|
|
|
File dataFile = new File(dataLocation);
|
|
|
|
|
2014-12-12 01:03:24 -08:00
|
|
|
InputStream is;
|
2013-04-26 11:23:43 -07:00
|
|
|
|
2017-03-28 12:05:30 -07:00
|
|
|
if (encrypted) is = DecryptingPartInputStream.createFor(masterSecret, dataFile);
|
2014-12-12 01:03:24 -08:00
|
|
|
else is = new FileInputStream(dataFile);
|
2013-04-26 11:23:43 -07:00
|
|
|
|
2014-12-12 01:03:24 -08:00
|
|
|
body = (body == null) ? Util.readFullyAsString(is) : body + " " + Util.readFullyAsString(is);
|
2013-04-26 11:23:43 -07:00
|
|
|
|
2015-10-12 18:25:05 -07:00
|
|
|
//noinspection ResultOfMethodCallIgnored
|
2013-04-26 11:23:43 -07:00
|
|
|
dataFile.delete();
|
|
|
|
db.delete("part", "_id = ?", new String[] {partId+""});
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w("DatabaseFactory", e);
|
|
|
|
}
|
2017-05-08 15:32:59 -07:00
|
|
|
} else if (MediaUtil.isAudioType(contentType) ||
|
|
|
|
MediaUtil.isImageType(contentType) ||
|
|
|
|
MediaUtil.isVideoType(contentType))
|
2013-04-26 11:23:43 -07:00
|
|
|
{
|
|
|
|
partCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-12 11:15:05 -08:00
|
|
|
if (!TextUtils.isEmpty(body)) {
|
2013-04-26 11:23:43 -07:00
|
|
|
body = masterCipher.encryptBody(body);
|
|
|
|
db.execSQL("UPDATE mms SET body = ?, part_count = ? WHERE _id = ?",
|
|
|
|
new String[] {body, partCount+"", mmsId+""});
|
|
|
|
} else {
|
|
|
|
db.execSQL("UPDATE mms SET part_count = ? WHERE _id = ?",
|
|
|
|
new String[] {partCount+"", mmsId+""});
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.w("DatabaseFactory", "Updated body: " + body + " and part_count: " + partCount);
|
|
|
|
}
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
}
|
2013-05-16 13:16:42 -07:00
|
|
|
|
2013-05-23 16:36:24 -07:00
|
|
|
if (fromVersion < DatabaseUpgradeActivity.TOFU_IDENTITIES_VERSION) {
|
|
|
|
File sessionDirectory = new File(context.getFilesDir() + File.separator + "sessions");
|
|
|
|
|
|
|
|
if (sessionDirectory.exists() && sessionDirectory.isDirectory()) {
|
|
|
|
File[] sessions = sessionDirectory.listFiles();
|
|
|
|
|
|
|
|
if (sessions != null) {
|
|
|
|
for (File session : sessions) {
|
|
|
|
String name = session.getName();
|
|
|
|
|
|
|
|
if (name.matches("[0-9]+")) {
|
2014-04-22 14:33:29 -07:00
|
|
|
long recipientId = Long.parseLong(name);
|
|
|
|
IdentityKey identityKey = null;
|
|
|
|
// NOTE (4/21/14) -- At this moment in time, we're forgetting the ability to parse
|
|
|
|
// V1 session records. Despite our usual attempts to avoid using shared code in the
|
|
|
|
// upgrade path, this is too complex to put here directly. Thus, unfortunately
|
|
|
|
// this operation is now lost to the ages. From the git log, it seems to have been
|
|
|
|
// almost exactly a year since this went in, so hopefully the bulk of people have
|
|
|
|
// already upgraded.
|
|
|
|
// IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId);
|
2013-05-23 16:36:24 -07:00
|
|
|
|
|
|
|
if (identityKey != null) {
|
|
|
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
|
|
|
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
|
|
|
|
String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
|
|
|
|
identityKeyString));
|
|
|
|
|
|
|
|
db.execSQL("REPLACE INTO identities (recipient, key, mac) VALUES (?, ?, ?)",
|
|
|
|
new String[] {recipientId+"", identityKeyString, macString});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-18 20:29:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fromVersion < DatabaseUpgradeActivity.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) {
|
|
|
|
if (!MasterSecretUtil.hasAsymmericMasterSecret(context)) {
|
|
|
|
MasterSecretUtil.generateAsymmetricMasterSecret(context, masterSecret);
|
|
|
|
|
|
|
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
|
|
|
Cursor cursor = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
cursor = db.query(SmsDatabase.TABLE_NAME,
|
|
|
|
new String[] {SmsDatabase.ID, SmsDatabase.BODY, SmsDatabase.TYPE},
|
|
|
|
SmsDatabase.TYPE + " & ? == 0",
|
|
|
|
new String[] {String.valueOf(SmsDatabase.Types.ENCRYPTION_MASK)},
|
|
|
|
null, null, null);
|
|
|
|
|
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String body = cursor.getString(1);
|
|
|
|
long type = cursor.getLong(2);
|
|
|
|
|
|
|
|
String encryptedBody = masterCipher.encryptBody(body);
|
|
|
|
|
|
|
|
ContentValues update = new ContentValues();
|
|
|
|
update.put(SmsDatabase.BODY, encryptedBody);
|
|
|
|
update.put(SmsDatabase.TYPE, type | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT);
|
|
|
|
|
|
|
|
db.update(SmsDatabase.TABLE_NAME, update, SmsDatabase.ID + " = ?",
|
|
|
|
new String[] {String.valueOf(id)});
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (cursor != null)
|
|
|
|
cursor.close();
|
|
|
|
}
|
2013-05-23 16:36:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.setTransactionSuccessful();
|
|
|
|
db.endTransaction();
|
|
|
|
|
2014-11-03 15:16:04 -08:00
|
|
|
// DecryptingQueue.schedulePendingDecrypts(context, masterSecret);
|
2013-05-16 13:16:42 -07:00
|
|
|
MessageNotifier.updateNotification(context, masterSecret);
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
}
|
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
private static class DatabaseHelper extends SQLiteOpenHelper {
|
|
|
|
|
2017-07-26 09:59:15 -07:00
|
|
|
private static final String TAG = DatabaseHelper.class.getSimpleName();
|
|
|
|
|
|
|
|
private final Context context;
|
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
public DatabaseHelper(Context context, String name, CursorFactory factory, int version) {
|
|
|
|
super(context, name, factory, version);
|
2017-07-26 09:59:15 -07:00
|
|
|
this.context = context.getApplicationContext();
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-10-29 17:41:06 -07:00
|
|
|
public void onCreate(SQLiteDatabase db) {
|
2011-12-20 10:20:44 -08:00
|
|
|
db.execSQL(SmsDatabase.CREATE_TABLE);
|
|
|
|
db.execSQL(MmsDatabase.CREATE_TABLE);
|
2015-10-12 18:25:05 -07:00
|
|
|
db.execSQL(AttachmentDatabase.CREATE_TABLE);
|
2011-12-20 10:20:44 -08:00
|
|
|
db.execSQL(ThreadDatabase.CREATE_TABLE);
|
|
|
|
db.execSQL(IdentityDatabase.CREATE_TABLE);
|
2013-02-04 00:13:07 -08:00
|
|
|
db.execSQL(DraftDatabase.CREATE_TABLE);
|
2013-09-08 18:19:05 -07:00
|
|
|
db.execSQL(PushDatabase.CREATE_TABLE);
|
2014-01-14 00:26:43 -08:00
|
|
|
db.execSQL(GroupDatabase.CREATE_TABLE);
|
2017-08-21 18:37:39 -07:00
|
|
|
db.execSQL(RecipientDatabase.CREATE_TABLE);
|
2012-10-29 17:41:06 -07:00
|
|
|
|
|
|
|
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
|
|
|
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
2015-10-12 18:25:05 -07:00
|
|
|
executeStatements(db, AttachmentDatabase.CREATE_INDEXS);
|
2012-10-29 17:41:06 -07:00
|
|
|
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
|
2013-02-04 00:13:07 -08:00
|
|
|
executeStatements(db, DraftDatabase.CREATE_INDEXS);
|
2014-01-14 00:26:43 -08:00
|
|
|
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-09-30 19:56:29 -07:00
|
|
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
2013-05-05 12:51:36 -07:00
|
|
|
db.beginTransaction();
|
|
|
|
|
2012-10-29 17:41:06 -07:00
|
|
|
if (oldVersion < INTRODUCED_IDENTITIES_VERSION) {
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, key TEXT UNIQUE, name TEXT UNIQUE, mac TEXT);");
|
2012-10-29 17:41:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (oldVersion < INTRODUCED_INDEXES_VERSION) {
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
executeStatements(db, new String[] {
|
|
|
|
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON sms (thread_id);",
|
|
|
|
"CREATE INDEX IF NOT EXISTS sms_read_index ON sms (read);",
|
|
|
|
"CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON sms (read,thread_id);",
|
|
|
|
"CREATE INDEX IF NOT EXISTS sms_type_index ON sms (type);"
|
|
|
|
});
|
|
|
|
executeStatements(db, new String[] {
|
|
|
|
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON mms (thread_id);",
|
|
|
|
"CREATE INDEX IF NOT EXISTS mms_read_index ON mms (read);",
|
|
|
|
"CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON mms (read,thread_id);",
|
|
|
|
"CREATE INDEX IF NOT EXISTS mms_message_box_index ON mms (msg_box);"
|
|
|
|
});
|
|
|
|
executeStatements(db, new String[] {
|
|
|
|
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON part (mid);"
|
|
|
|
});
|
|
|
|
executeStatements(db, new String[] {
|
|
|
|
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON thread (recipient_ids);",
|
|
|
|
});
|
|
|
|
executeStatements(db, new String[] {
|
|
|
|
"CREATE INDEX IF NOT EXISTS mms_addresses_mms_id_index ON mms_addresses (mms_id);",
|
|
|
|
});
|
2012-10-29 17:41:06 -07:00
|
|
|
}
|
2013-01-06 13:13:14 -08:00
|
|
|
|
|
|
|
if (oldVersion < INTRODUCED_DATE_SENT_VERSION) {
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN date_sent INTEGER;");
|
|
|
|
db.execSQL("UPDATE sms SET date_sent = date;");
|
|
|
|
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN date_received INTEGER;");
|
|
|
|
db.execSQL("UPDATE mms SET date_received = date;");
|
2013-01-06 13:13:14 -08:00
|
|
|
}
|
2013-02-04 00:13:07 -08:00
|
|
|
|
|
|
|
if (oldVersion < INTRODUCED_DRAFTS_VERSION) {
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
db.execSQL("CREATE TABLE drafts (_id INTEGER PRIMARY KEY, thread_id INTEGER, type TEXT, value TEXT);");
|
|
|
|
executeStatements(db, new String[] {
|
|
|
|
"CREATE INDEX IF NOT EXISTS draft_thread_index ON drafts (thread_id);",
|
|
|
|
});
|
2013-02-04 00:13:07 -08:00
|
|
|
}
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
|
|
|
if (oldVersion < INTRODUCED_NEW_TYPES_VERSION) {
|
|
|
|
String KEY_EXCHANGE = "?TextSecureKeyExchange";
|
|
|
|
String SYMMETRIC_ENCRYPT = "?TextSecureLocalEncrypt";
|
|
|
|
String ASYMMETRIC_ENCRYPT = "?TextSecureAsymmetricEncrypt";
|
|
|
|
String ASYMMETRIC_LOCAL_ENCRYPT = "?TextSecureAsymmetricLocalEncrypt";
|
|
|
|
String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd";
|
|
|
|
String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs";
|
|
|
|
|
|
|
|
// SMS Updates
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {20L+"", 1L+""});
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {21L+"", 43L+""});
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {22L+"", 4L+""});
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {23L+"", 2L+""});
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {24L+"", 5L+""});
|
|
|
|
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(21L | 0x800000L)+"", 42L+""});
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(23L | 0x800000L)+"", 44L+""});
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L)+"", 45L+""});
|
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x10000000L)+"", 46L+""});
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L)+"", 47L+""});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x08000000L)+"", 48L+""});
|
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?",
|
|
|
|
new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"",
|
|
|
|
0x80000000L+"",
|
|
|
|
SYMMETRIC_ENCRYPT + "%"});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?",
|
|
|
|
new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"",
|
|
|
|
0x40000000L+"",
|
|
|
|
ASYMMETRIC_LOCAL_ENCRYPT + "%"});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?",
|
|
|
|
new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"",
|
|
|
|
(0x800000L | 0x20000000L)+"",
|
|
|
|
ASYMMETRIC_ENCRYPT + "%"});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?",
|
|
|
|
new String[] {(KEY_EXCHANGE.length()+1)+"",
|
|
|
|
0x8000L+"",
|
|
|
|
KEY_EXCHANGE + "%"});
|
2013-05-05 12:51:36 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?",
|
|
|
|
new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"",
|
|
|
|
(0x8000L | 0x2000L)+"",
|
|
|
|
PROCESSED_KEY_EXCHANGE + "%"});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?",
|
|
|
|
new String[] {(STALE_KEY_EXCHANGE.length()+1)+"",
|
|
|
|
(0x8000L | 0x4000L)+"",
|
|
|
|
STALE_KEY_EXCHANGE + "%"});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
|
|
|
// MMS Updates
|
|
|
|
|
2013-04-26 11:23:43 -07:00
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L)+"", 1+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L)+"", 2+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L)+"", 4+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(24L | 0x80000000L)+"", 12+""});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
2013-04-26 11:23:43 -07:00
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L | 0x800000L) +"", 5+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L | 0x800000L) +"", 6+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x20000000L | 0x800000L) +"", 7+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L | 0x800000L) +"", 8+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x08000000L | 0x800000L) +"", 9+""});
|
|
|
|
db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x10000000L | 0x800000L) +"", 10+""});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
|
|
|
|
// Thread Updates
|
|
|
|
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN snippet_type INTEGER;");
|
|
|
|
|
2013-05-16 13:16:42 -07:00
|
|
|
db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " +
|
|
|
|
"snippet_type = ? WHERE snippet LIKE ?",
|
|
|
|
new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"",
|
|
|
|
0x80000000L+"",
|
|
|
|
SYMMETRIC_ENCRYPT + "%"});
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " +
|
|
|
|
"snippet_type = ? WHERE snippet LIKE ?",
|
|
|
|
new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"",
|
|
|
|
0x40000000L+"",
|
|
|
|
ASYMMETRIC_LOCAL_ENCRYPT + "%"});
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " +
|
|
|
|
"snippet_type = ? WHERE snippet LIKE ?",
|
|
|
|
new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"",
|
|
|
|
(0x800000L | 0x20000000L)+"",
|
|
|
|
ASYMMETRIC_ENCRYPT + "%"});
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " +
|
|
|
|
"snippet_type = ? WHERE snippet LIKE ?",
|
|
|
|
new String[] {(KEY_EXCHANGE.length()+1)+"",
|
|
|
|
0x8000L+"",
|
|
|
|
KEY_EXCHANGE + "%"});
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " +
|
|
|
|
"snippet_type = ? WHERE snippet LIKE ?",
|
|
|
|
new String[] {(STALE_KEY_EXCHANGE.length()+1)+"",
|
|
|
|
(0x8000L | 0x4000L)+"",
|
|
|
|
STALE_KEY_EXCHANGE + "%"});
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " +
|
|
|
|
"snippet_type = ? WHERE snippet LIKE ?",
|
|
|
|
new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"",
|
|
|
|
(0x8000L | 0x2000L)+"",
|
|
|
|
PROCESSED_KEY_EXCHANGE + "%"});
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
}
|
2013-04-26 11:23:43 -07:00
|
|
|
|
|
|
|
if (oldVersion < INTRODUCED_MMS_BODY_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN body TEXT");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN part_count INTEGER");
|
|
|
|
}
|
2013-05-05 12:51:36 -07:00
|
|
|
|
|
|
|
if (oldVersion < INTRODUCED_MMS_FROM_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN address TEXT");
|
|
|
|
|
|
|
|
Cursor cursor = db.query("mms_addresses", null, "type = ?", new String[] {0x89+""},
|
|
|
|
null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long mmsId = cursor.getLong(cursor.getColumnIndexOrThrow("mms_id"));
|
|
|
|
String address = cursor.getString(cursor.getColumnIndexOrThrow("address"));
|
|
|
|
|
2014-11-12 11:15:05 -08:00
|
|
|
if (!TextUtils.isEmpty(address)) {
|
2013-05-05 12:51:36 -07:00
|
|
|
db.execSQL("UPDATE mms SET address = ? WHERE _id = ?", new String[]{address, mmsId+""});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null)
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
|
2013-05-23 16:36:24 -07:00
|
|
|
if (oldVersion < INTRODUCED_TOFU_IDENTITY_VERSION) {
|
|
|
|
db.execSQL("DROP TABLE identities");
|
|
|
|
db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, recipient INTEGER UNIQUE, key TEXT, mac TEXT);");
|
|
|
|
}
|
|
|
|
|
2013-09-08 18:19:05 -07:00
|
|
|
if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) {
|
|
|
|
db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);");
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;");
|
2013-09-14 13:33:23 -07:00
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON part (pending_push);");
|
2013-09-08 18:19:05 -07:00
|
|
|
}
|
|
|
|
|
2014-01-14 00:26:43 -08:00
|
|
|
if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) {
|
2014-02-22 11:29:28 -08:00
|
|
|
db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, avatar_relay TEXT, timestamp INTEGER, active INTEGER DEFAULT 1);");
|
2014-01-14 00:26:43 -08:00
|
|
|
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);");
|
2014-02-02 19:38:06 -08:00
|
|
|
db.execSQL("ALTER TABLE push ADD COLUMN device_id INTEGER DEFAULT 1;");
|
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
|
2014-01-14 00:26:43 -08:00
|
|
|
}
|
|
|
|
|
2014-02-24 16:59:22 -08:00
|
|
|
if (oldVersion < INTRODUCED_PUSH_FIX_VERSION) {
|
|
|
|
db.execSQL("CREATE TEMPORARY table push_backup (_id INTEGER PRIMARY KEY, type INTEGER, source, TEXT, destinations TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);");
|
|
|
|
db.execSQL("INSERT INTO push_backup(_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push;");
|
|
|
|
db.execSQL("DROP TABLE push");
|
|
|
|
db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);");
|
|
|
|
db.execSQL("INSERT INTO push (_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push_backup;");
|
|
|
|
db.execSQL("DROP TABLE push_backup;");
|
|
|
|
}
|
|
|
|
|
2014-07-25 15:14:29 -07:00
|
|
|
if (oldVersion < INTRODUCED_DELIVERY_RECEIPTS) {
|
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;");
|
2014-07-27 19:42:44 -07:00
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS sms_date_sent_index ON sms (date_sent);");
|
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS mms_date_sent_index ON mms (date);");
|
2014-07-25 15:14:29 -07:00
|
|
|
}
|
|
|
|
|
2014-12-12 01:03:24 -08:00
|
|
|
if (oldVersion < INTRODUCED_PART_DATA_SIZE_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN data_size INTEGER DEFAULT 0;");
|
|
|
|
}
|
|
|
|
|
2014-12-17 11:47:19 -08:00
|
|
|
if (oldVersion < INTRODUCED_THUMBNAILS_VERSION) {
|
2015-01-15 13:35:35 -08:00
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN thumbnail TEXT;");
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN aspect_ratio REAL;");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldVersion < INTRODUCED_IDENTITY_COLUMN_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN mismatched_identities TEXT");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN mismatched_identities TEXT");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN network_failures TEXT");
|
2014-12-17 11:47:19 -08:00
|
|
|
}
|
|
|
|
|
2015-05-21 11:55:03 -07:00
|
|
|
if (oldVersion < INTRODUCED_UNIQUE_PART_IDS_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN unique_id INTEGER NOT NULL DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2015-06-09 07:37:20 -07:00
|
|
|
if (oldVersion < INTRODUCED_RECIPIENT_PREFS_DB) {
|
|
|
|
db.execSQL("CREATE TABLE recipient_preferences " +
|
|
|
|
"(_id INTEGER PRIMARY KEY, recipient_ids TEXT UNIQUE, block INTEGER DEFAULT 0, " +
|
|
|
|
"notification TEXT DEFAULT NULL, vibrate INTEGER DEFAULT 0, mute_until INTEGER DEFAULT 0)");
|
|
|
|
}
|
|
|
|
|
2015-05-29 16:23:47 -07:00
|
|
|
if (oldVersion < INTRODUCED_ENVELOPE_CONTENT_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE push ADD COLUMN content TEXT");
|
|
|
|
}
|
|
|
|
|
2015-07-01 12:27:25 -07:00
|
|
|
if (oldVersion < INTRODUCED_COLOR_PREFERENCE_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN color TEXT DEFAULT NULL");
|
|
|
|
}
|
|
|
|
|
2015-08-04 13:37:22 -07:00
|
|
|
if (oldVersion < INTRODUCED_DB_OPTIMIZATIONS_VERSION) {
|
|
|
|
db.execSQL("UPDATE mms SET date_received = (date_received * 1000), date = (date * 1000);");
|
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS sms_thread_date_index ON sms (thread_id, date);");
|
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS mms_thread_date_index ON mms (thread_id, date_received);");
|
|
|
|
}
|
|
|
|
|
2015-10-13 21:44:01 -07:00
|
|
|
if (oldVersion < INTRODUCED_INVITE_REMINDERS_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN seen_invite_reminder INTEGER DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2015-10-16 13:59:40 -07:00
|
|
|
if (oldVersion < INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN snippet_uri TEXT DEFAULT NULL");
|
|
|
|
}
|
|
|
|
|
2015-11-23 15:07:41 -08:00
|
|
|
if (oldVersion < INTRODUCED_ARCHIVE_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN archived INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS archived_index ON thread (archived)");
|
|
|
|
}
|
|
|
|
|
2015-11-24 16:06:41 +01:00
|
|
|
if (oldVersion < INTRODUCED_CONVERSATION_LIST_STATUS_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN status INTEGER DEFAULT -1");
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2015-12-04 22:47:33 +01:00
|
|
|
if (oldVersion < MIGRATED_CONVERSATION_LIST_STATUS_VERSION) {
|
|
|
|
Cursor threadCursor = db.query("thread", new String[] {"_id"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (threadCursor != null && threadCursor.moveToNext()) {
|
|
|
|
long threadId = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id"));
|
|
|
|
|
|
|
|
Cursor cursor = db.rawQuery("SELECT DISTINCT date AS date_received, status, " +
|
|
|
|
"delivery_receipt_count FROM sms WHERE (thread_id = ?1) " +
|
|
|
|
"UNION ALL SELECT DISTINCT date_received, -1 AS status, " +
|
|
|
|
"delivery_receipt_count FROM mms WHERE (thread_id = ?1) " +
|
|
|
|
"ORDER BY date_received DESC LIMIT 1", new String[]{threadId + ""});
|
|
|
|
|
|
|
|
if (cursor != null && cursor.moveToNext()) {
|
|
|
|
int status = cursor.getInt(cursor.getColumnIndexOrThrow("status"));
|
|
|
|
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow("delivery_receipt_count"));
|
|
|
|
|
|
|
|
db.execSQL("UPDATE thread SET status = ?, delivery_receipt_count = ? WHERE _id = ?",
|
|
|
|
new String[]{status + "", receiptCount + "", threadId + ""});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-05 16:10:33 -08:00
|
|
|
if (oldVersion < INTRODUCED_SUBSCRIPTION_ID_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN default_subscription_id INTEGER DEFAULT -1");
|
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN subscription_id INTEGER DEFAULT -1");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1");
|
|
|
|
}
|
|
|
|
|
2016-08-15 20:23:56 -07:00
|
|
|
if (oldVersion < INTRODUCED_EXPIRE_MESSAGES_VERSION) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN expire_messages INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN expires_in INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN expires_in INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN expire_started INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN expire_started INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN expires_in INTEGER DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2017-02-13 22:35:47 -08:00
|
|
|
if (oldVersion < INTRODUCED_LAST_SEEN) {
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN last_seen INTEGER DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2017-02-26 10:06:27 -08:00
|
|
|
if (oldVersion < INTRODUCED_DIGEST) {
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN digest BLOB");
|
|
|
|
db.execSQL("ALTER TABLE groups ADD COLUMN avatar_digest BLOB");
|
|
|
|
}
|
|
|
|
|
2017-03-08 17:38:55 -08:00
|
|
|
if (oldVersion < INTRODUCED_NOTIFIED) {
|
|
|
|
db.execSQL("ALTER TABLE sms ADD COLUMN notified INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE mms ADD COLUMN notified INTEGER DEFAULT 0");
|
|
|
|
|
|
|
|
db.execSQL("DROP INDEX sms_read_and_thread_id_index");
|
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS sms_read_and_notified_and_thread_id_index ON sms(read,notified,thread_id)");
|
|
|
|
|
|
|
|
db.execSQL("DROP INDEX mms_read_and_thread_id_index");
|
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS mms_read_and_notified_and_thread_id_index ON mms(read,notified,thread_id)");
|
|
|
|
}
|
|
|
|
|
2017-03-28 12:05:30 -07:00
|
|
|
if (oldVersion < INTRODUCED_DOCUMENTS) {
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN file_name TEXT");
|
|
|
|
}
|
|
|
|
|
2017-04-22 16:29:26 -07:00
|
|
|
if (oldVersion < INTRODUCED_FAST_PREFLIGHT) {
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN fast_preflight_id TEXT");
|
|
|
|
}
|
|
|
|
|
2017-05-11 22:46:35 -07:00
|
|
|
if (oldVersion < INTRODUCED_VOICE_NOTES) {
|
|
|
|
db.execSQL("ALTER TABLE part ADD COLUMN voice_note INTEGER DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2017-05-19 18:01:40 -07:00
|
|
|
if (oldVersion < INTRODUCED_IDENTITY_TIMESTAMP) {
|
|
|
|
db.execSQL("ALTER TABLE identities ADD COLUMN timestamp INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE identities ADD COLUMN first_use INTEGER DEFAULT 0");
|
|
|
|
db.execSQL("ALTER TABLE identities ADD COLUMN nonblocking_approval INTEGER DEFAULT 0");
|
2017-06-15 12:04:50 -07:00
|
|
|
db.execSQL("ALTER TABLE identities ADD COLUMN verified INTEGER DEFAULT 0");
|
2017-05-19 18:01:40 -07:00
|
|
|
|
2017-06-01 10:57:45 -07:00
|
|
|
db.execSQL("DROP INDEX archived_index");
|
2017-05-19 18:01:40 -07:00
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (archived, message_count)");
|
|
|
|
}
|
|
|
|
|
2017-07-21 15:59:27 -07:00
|
|
|
if (oldVersion < SANIFY_ATTACHMENT_DOWNLOAD) {
|
|
|
|
db.execSQL("UPDATE part SET pending_push = '2' WHERE pending_push = '1'");
|
|
|
|
}
|
|
|
|
|
2017-07-26 09:59:15 -07:00
|
|
|
if (oldVersion < NO_MORE_CANONICAL_ADDRESS_DATABASE) {
|
|
|
|
DatabaseHelper canonicalAddressDatabaseHelper = new DatabaseHelper(context, "canonical_address.db", null, 1);
|
|
|
|
SQLiteDatabase canonicalAddressDatabase = canonicalAddressDatabaseHelper.getReadableDatabase();
|
|
|
|
NumberMigrator numberMigrator = new NumberMigrator(TextSecurePreferences.getLocalNumber(context));
|
|
|
|
|
|
|
|
// Migrate Thread Database
|
|
|
|
Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long threadId = cursor.getLong(0);
|
|
|
|
String recipientIdsList = cursor.getString(1);
|
|
|
|
String[] recipientIds = recipientIdsList.split(" ");
|
2017-07-31 14:04:47 -07:00
|
|
|
String[] addresses = new String[recipientIds.length];
|
2017-07-26 09:59:15 -07:00
|
|
|
|
|
|
|
for (int i=0;i<recipientIds.length;i++) {
|
|
|
|
Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {recipientIds[i]}, null, null, null);
|
|
|
|
|
|
|
|
if (resolved != null && resolved.moveToFirst()) {
|
|
|
|
String address = resolved.getString(0);
|
2017-08-02 11:08:38 -07:00
|
|
|
addresses[i] = DelimiterUtil.escape(numberMigrator.migrate(address), ' ');
|
2017-08-03 09:32:56 -07:00
|
|
|
} else if (TextUtils.isEmpty(recipientIds[i]) || recipientIds[i].equals("-1")) {
|
2017-08-01 18:45:13 -07:00
|
|
|
addresses[i] = "Unknown";
|
2017-07-26 09:59:15 -07:00
|
|
|
} else {
|
2017-08-03 09:32:56 -07:00
|
|
|
throw new AssertionError("Unable to resolve: " + recipientIds[i] + ", recipientIdsList: '" + recipientIdsList + "'");
|
2017-07-26 09:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (resolved != null) resolved.close();
|
|
|
|
}
|
|
|
|
|
2017-08-02 11:08:38 -07:00
|
|
|
Arrays.sort(addresses);
|
|
|
|
|
2017-07-26 09:59:15 -07:00
|
|
|
ContentValues values = new ContentValues(1);
|
2017-07-31 14:04:47 -07:00
|
|
|
values.put("recipient_ids", Util.join(addresses, " "));
|
2017-07-26 09:59:15 -07:00
|
|
|
db.update("thread", values, "_id = ?", new String[] {String.valueOf(threadId)});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
// Migrate Identity database
|
|
|
|
db.execSQL("CREATE TABLE identities_migrated (_id INTEGER PRIMARY KEY, address TEXT UNIQUE, key TEXT, first_use INTEGER DEFAULT 0, timestamp INTEGER DEFAULT 0, verified INTEGER DEFAULT 0, nonblocking_approval INTEGER DEFAULT 0);");
|
|
|
|
|
|
|
|
cursor = db.query("identities", new String[] {"_id, recipient, key, first_use, timestamp, verified, nonblocking_approval"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
long recipientId = cursor.getLong(1);
|
|
|
|
String key = cursor.getString(2);
|
|
|
|
int firstUse = cursor.getInt(3);
|
|
|
|
long timestamp = cursor.getLong(4);
|
|
|
|
int verified = cursor.getInt(5);
|
|
|
|
int nonblockingApproval = cursor.getInt(6);
|
|
|
|
|
|
|
|
ContentValues values = new ContentValues(6);
|
|
|
|
|
|
|
|
Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(recipientId)}, null, null, null);
|
|
|
|
|
|
|
|
if (resolved != null && resolved.moveToFirst()) {
|
|
|
|
String address = resolved.getString(0);
|
|
|
|
values.put("address", numberMigrator.migrate(address));
|
|
|
|
values.put("key", key);
|
|
|
|
values.put("first_use", firstUse);
|
|
|
|
values.put("timestamp", timestamp);
|
|
|
|
values.put("verified", verified);
|
|
|
|
values.put("nonblocking_approval", nonblockingApproval);
|
|
|
|
} else {
|
|
|
|
throw new AssertionError("Unable to resolve: " + recipientId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resolved != null) resolved.close();
|
|
|
|
|
|
|
|
db.insert("identities_migrated", null, values);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
db.execSQL("DROP TABLE identities");
|
|
|
|
db.execSQL("ALTER TABLE identities_migrated RENAME TO identities");
|
|
|
|
|
|
|
|
// Migrate recipient preferences database
|
|
|
|
cursor = db.query("recipient_preferences", new String[] {"_id", "recipient_ids"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String recipientIdsList = cursor.getString(1);
|
|
|
|
String[] recipientIds = recipientIdsList.split(" ");
|
|
|
|
String[] addresses = new String[recipientIds.length];
|
|
|
|
|
|
|
|
for (int i=0;i<recipientIds.length;i++) {
|
|
|
|
Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {recipientIds[i]}, null, null, null);
|
|
|
|
|
|
|
|
if (resolved != null && resolved.moveToFirst()) {
|
|
|
|
String address = resolved.getString(0);
|
2017-08-02 11:08:38 -07:00
|
|
|
addresses[i] = DelimiterUtil.escape(numberMigrator.migrate(address), ' ');
|
2017-08-03 09:32:56 -07:00
|
|
|
} else if (TextUtils.isEmpty(recipientIds[i]) || recipientIds[i].equals("-1")) {
|
2017-08-01 18:45:13 -07:00
|
|
|
addresses[i] = "Unknown";
|
2017-07-26 09:59:15 -07:00
|
|
|
} else {
|
2017-08-03 09:32:56 -07:00
|
|
|
throw new AssertionError("Unable to resolve: " + recipientIds[i] + ", recipientIdsList: '" + recipientIdsList + "'");
|
2017-07-26 09:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (resolved != null) resolved.close();
|
|
|
|
}
|
|
|
|
|
2017-08-02 11:08:38 -07:00
|
|
|
Arrays.sort(addresses);
|
|
|
|
|
2017-07-26 09:59:15 -07:00
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put("recipient_ids", Util.join(addresses, " "));
|
2017-08-01 14:49:16 -07:00
|
|
|
|
|
|
|
try {
|
|
|
|
db.update("recipient_preferences", values, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
} catch (SQLiteConstraintException e) {
|
|
|
|
Log.w(TAG, e);
|
2017-08-02 08:02:15 -07:00
|
|
|
db.delete("recipient_preferences", "_id = ?", new String[] {String.valueOf(id)});
|
2017-08-01 14:49:16 -07:00
|
|
|
}
|
2017-07-26 09:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
// Migrate SMS database
|
|
|
|
cursor = db.query("sms", new String[] {"_id", "address"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String address = cursor.getString(1);
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(address)) {
|
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put("address", numberMigrator.migrate(address));
|
|
|
|
db.update("sms", values, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
// Migrate MMS database
|
|
|
|
cursor = db.query("mms", new String[] {"_id", "address"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String address = cursor.getString(1);
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(address)) {
|
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put("address", numberMigrator.migrate(address));
|
|
|
|
db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
// Migrate MmsAddressDatabase
|
|
|
|
cursor = db.query("mms_addresses", new String[] {"_id", "address"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String address = cursor.getString(1);
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(address) && !"insert-address-token".equals(address)) {
|
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put("address", numberMigrator.migrate(address));
|
|
|
|
db.update("mms_addresses", values, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
// Migrate SMS mismatched identities
|
|
|
|
cursor = db.query("sms", new String[] {"_id", "mismatched_identities"}, "mismatched_identities IS NOT NULL", null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String document = cursor.getString(1);
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(document)) {
|
|
|
|
try {
|
|
|
|
PreCanonicalAddressIdentityMismatchList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressIdentityMismatchList.class);
|
|
|
|
List<PostCanonicalAddressIdentityMismatchDocument> newDocumentList = new LinkedList<>();
|
|
|
|
|
|
|
|
for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) {
|
|
|
|
Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null);
|
|
|
|
|
|
|
|
if (resolved != null && resolved.moveToFirst()) {
|
|
|
|
String address = resolved.getString(0);
|
|
|
|
newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey));
|
|
|
|
} else {
|
|
|
|
throw new AssertionError("Unable to resolve: " + oldDocument.recipientId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resolved != null) resolved.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList)));
|
|
|
|
db.update("sms", values, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
// Migrate MMS mismatched identities
|
|
|
|
cursor = db.query("mms", new String[] {"_id", "mismatched_identities"}, "mismatched_identities IS NOT NULL", null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String document = cursor.getString(1);
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(document)) {
|
|
|
|
try {
|
|
|
|
PreCanonicalAddressIdentityMismatchList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressIdentityMismatchList.class);
|
|
|
|
List<PostCanonicalAddressIdentityMismatchDocument> newDocumentList = new LinkedList<>();
|
|
|
|
|
|
|
|
for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) {
|
|
|
|
Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null);
|
|
|
|
|
|
|
|
if (resolved != null && resolved.moveToFirst()) {
|
|
|
|
String address = resolved.getString(0);
|
|
|
|
newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey));
|
|
|
|
} else {
|
|
|
|
throw new AssertionError("Unable to resolve: " + oldDocument.recipientId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resolved != null) resolved.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList)));
|
|
|
|
db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
// Migrate MMS network failures
|
|
|
|
cursor = db.query("mms", new String[] {"_id", "network_failures"}, "network_failures IS NOT NULL", null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String document = cursor.getString(1);
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(document)) {
|
|
|
|
try {
|
|
|
|
PreCanonicalAddressNetworkFailureList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressNetworkFailureList.class);
|
|
|
|
List<PostCanonicalAddressNetworkFailureDocument> newDocumentList = new LinkedList<>();
|
|
|
|
|
|
|
|
for (PreCanonicalAddressNetworkFailureDocument oldDocument : oldDocumentList.list) {
|
|
|
|
Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null);
|
|
|
|
|
|
|
|
if (resolved != null && resolved.moveToFirst()) {
|
|
|
|
String address = resolved.getString(0);
|
|
|
|
newDocumentList.add(new PostCanonicalAddressNetworkFailureDocument(numberMigrator.migrate(address)));
|
|
|
|
} else {
|
|
|
|
throw new AssertionError("Unable to resolve: " + oldDocument.recipientId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resolved != null) resolved.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put("network_failures", JsonUtils.toJson(new PostCanonicalAddressNetworkFailureList(newDocumentList)));
|
|
|
|
db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Migrate sessions
|
|
|
|
File sessionsDirectory = new File(context.getFilesDir(), "sessions-v2");
|
|
|
|
|
|
|
|
if (sessionsDirectory.exists() && sessionsDirectory.isDirectory()) {
|
|
|
|
File[] sessions = sessionsDirectory.listFiles();
|
|
|
|
|
|
|
|
for (File session : sessions) {
|
|
|
|
try {
|
|
|
|
String[] sessionParts = session.getName().split("[.]");
|
|
|
|
long recipientId = Long.parseLong(sessionParts[0]);
|
|
|
|
|
|
|
|
int deviceId;
|
|
|
|
|
|
|
|
if (sessionParts.length > 1) deviceId = Integer.parseInt(sessionParts[1]);
|
|
|
|
else deviceId = 1;
|
|
|
|
|
|
|
|
Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(recipientId)}, null, null, null);
|
|
|
|
|
|
|
|
if (resolved != null && resolved.moveToNext()) {
|
|
|
|
String address = resolved.getString(0);
|
|
|
|
File destination = new File(session.getParentFile(), address + (deviceId != 1 ? "." + deviceId : ""));
|
|
|
|
|
|
|
|
if (!session.renameTo(destination)) {
|
|
|
|
Log.w(TAG, "Session rename failed: " + destination);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resolved != null) resolved.close();
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-08-01 08:56:00 -07:00
|
|
|
if (oldVersion < NO_MORE_RECIPIENTS_PLURAL) {
|
|
|
|
db.execSQL("ALTER TABLE groups ADD COLUMN mms INTEGER DEFAULT 0");
|
|
|
|
|
|
|
|
Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long threadId = cursor.getLong(0);
|
|
|
|
String addressListString = cursor.getString(1);
|
|
|
|
String[] addressList = DelimiterUtil.split(addressListString, ' ');
|
|
|
|
|
|
|
|
if (addressList.length == 1) {
|
|
|
|
ContentValues contentValues = new ContentValues();
|
|
|
|
contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' '));
|
|
|
|
db.update("thread", contentValues, "_id = ?", new String[] {String.valueOf(threadId)});
|
|
|
|
} else {
|
|
|
|
byte[] groupId = new byte[16];
|
|
|
|
List<String> members = new LinkedList<>();
|
|
|
|
|
|
|
|
new SecureRandom().nextBytes(groupId);
|
|
|
|
|
|
|
|
for (String address : addressList) {
|
|
|
|
members.add(DelimiterUtil.escape(DelimiterUtil.unescape(address, ' '), ','));
|
|
|
|
}
|
|
|
|
|
2017-09-06 16:08:03 -07:00
|
|
|
members.add(DelimiterUtil.escape(TextSecurePreferences.getLocalNumber(context), ','));
|
|
|
|
|
2017-08-01 08:56:00 -07:00
|
|
|
String encodedGroupId = "__signal_mms_group__!" + Hex.toStringCondensed(groupId);
|
|
|
|
ContentValues groupValues = new ContentValues();
|
|
|
|
ContentValues threadValues = new ContentValues();
|
|
|
|
|
|
|
|
groupValues.put("group_id", encodedGroupId);
|
|
|
|
groupValues.put("members", Util.join(members, ","));
|
|
|
|
groupValues.put("mms", 1);
|
|
|
|
|
|
|
|
threadValues.put("recipient_ids", encodedGroupId);
|
|
|
|
|
|
|
|
db.insert("groups", null, groupValues);
|
|
|
|
db.update("thread", threadValues, "_id = ?", new String[] {String.valueOf(threadId)});
|
|
|
|
db.update("recipient_preferences", threadValues, "recipient_ids = ?", new String[] {addressListString});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
cursor = db.query("recipient_preferences", new String[] {"_id", "recipient_ids"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
String addressListString = cursor.getString(1);
|
|
|
|
String[] addressList = DelimiterUtil.split(addressListString, ' ');
|
|
|
|
|
|
|
|
if (addressList.length == 1) {
|
|
|
|
ContentValues contentValues = new ContentValues();
|
|
|
|
contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' '));
|
|
|
|
db.update("recipient_preferences", contentValues, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
} else {
|
|
|
|
Log.w(TAG, "Found preferences for MMS thread that appears to be gone: " + addressListString);
|
|
|
|
db.delete("recipient_preferences", "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
|
|
|
cursor = db.rawQuery("SELECT mms._id, thread.recipient_ids FROM mms, thread WHERE mms.address IS NULL AND mms.thread_id = thread._id", null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
long id = cursor.getLong(0);
|
|
|
|
ContentValues contentValues = new ContentValues(1);
|
|
|
|
|
|
|
|
contentValues.put("address", cursor.getString(1));
|
|
|
|
db.update("mms", contentValues, "_id = ?", new String[] {String.valueOf(id)});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
}
|
|
|
|
|
2017-08-07 14:24:53 -07:00
|
|
|
if (oldVersion < INTERNAL_DIRECTORY) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN registered INTEGER DEFAULT 0");
|
|
|
|
|
|
|
|
DatabaseHelper directoryDatabaseHelper = new DatabaseHelper(context, "whisper_directory.db", null, 5);
|
|
|
|
SQLiteDatabase directoryDatabase = directoryDatabaseHelper.getReadableDatabase();
|
|
|
|
|
|
|
|
Cursor cursor = directoryDatabase.query("directory", new String[] {"number", "registered"}, null, null, null, null, null);
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
String address = new NumberMigrator(TextSecurePreferences.getLocalNumber(context)).migrate(cursor.getString(0));
|
|
|
|
ContentValues contentValues = new ContentValues(1);
|
|
|
|
|
2017-08-22 10:44:04 -07:00
|
|
|
contentValues.put("registered", cursor.getInt(1) == 1 ? 1 : 2);
|
2017-08-15 19:23:42 -07:00
|
|
|
|
|
|
|
if (db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address}) < 1) {
|
|
|
|
contentValues.put("recipient_ids", address);
|
|
|
|
db.insert("recipient_preferences", null, contentValues);
|
|
|
|
}
|
2017-08-07 14:24:53 -07:00
|
|
|
}
|
2017-09-08 11:36:09 -07:00
|
|
|
|
|
|
|
if (cursor != null) cursor.close();
|
2017-08-07 14:24:53 -07:00
|
|
|
}
|
|
|
|
|
2017-08-07 15:31:12 -07:00
|
|
|
if (oldVersion < INTERNAL_SYSTEM_DISPLAY_NAME) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_display_name TEXT DEFAULT NULL");
|
|
|
|
}
|
|
|
|
|
2017-08-14 18:11:13 -07:00
|
|
|
if (oldVersion < PROFILES) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_key TEXT DEFAULT NULL");
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_name TEXT DEFAULT NULL");
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_avatar TEXT DEFAULT NULL");
|
|
|
|
}
|
|
|
|
|
2017-08-16 21:49:41 -07:00
|
|
|
if (oldVersion < PROFILE_SHARING_APPROVAL) {
|
|
|
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_sharing_approval INTEGER DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2017-08-18 17:28:56 -07:00
|
|
|
if (oldVersion < UNSEEN_NUMBER_OFFER) {
|
|
|
|
db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0");
|
|
|
|
}
|
|
|
|
|
2013-05-05 12:51:36 -07:00
|
|
|
db.setTransactionSuccessful();
|
|
|
|
db.endTransaction();
|
Major storage layer refactoring to set the stage for clean GCM.
1) We now try to hand out cursors at a minimum. There has always been
a fairly clean insertion layer that handles encrypting message bodies,
but the process of decrypting message bodies has always been less than
ideal. Here we introduce a "Reader" interface that will decrypt message
bodies when appropriate and return objects that encapsulate record state.
No more MessageDisplayHelper. The MmsSmsDatabase interface is also more
sane.
2) We finally rid ourselves of the technical debt associated with TextSecure's
initial usage of the default SMS DB. In that world, we weren't able to use
anything other than the default "Inbox, Outbox, Sent" types to describe a
message, and had to overload the message content itself with a set of
local "prefixes" to describe what it was (encrypted, asymetric encrypted,
remote encrypted, a key exchange, procssed key exchange), and so on.
This includes a major schema update that transforms the "type" field into
a bitmask that describes everything that used to be encoded in a prefix,
and prefixes have been completely eliminated from the system.
No more Prefix.java
3) Refactoring of the MultipartMessageHandler code. It's less of a mess, and
hopefully more clear as to what's going on.
The next step is to remove what we can from SmsTransportDetails and genericize
that interface for a GCM equivalent.
2013-04-20 12:22:04 -07:00
|
|
|
}
|
|
|
|
|
2012-10-29 17:41:06 -07:00
|
|
|
private void executeStatements(SQLiteDatabase db, String[] statements) {
|
|
|
|
for (String statement : statements)
|
|
|
|
db.execSQL(statement);
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|
2012-10-29 17:41:06 -07:00
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|
2017-07-26 09:59:15 -07:00
|
|
|
|
|
|
|
private static class PreCanonicalAddressIdentityMismatchList {
|
|
|
|
@JsonProperty(value = "m")
|
|
|
|
private List<PreCanonicalAddressIdentityMismatchDocument> list;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class PostCanonicalAddressIdentityMismatchList {
|
|
|
|
@JsonProperty(value = "m")
|
|
|
|
private List<PostCanonicalAddressIdentityMismatchDocument> list;
|
|
|
|
|
|
|
|
public PostCanonicalAddressIdentityMismatchList(List<PostCanonicalAddressIdentityMismatchDocument> list) {
|
|
|
|
this.list = list;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class PreCanonicalAddressIdentityMismatchDocument {
|
|
|
|
@JsonProperty(value = "r")
|
|
|
|
private long recipientId;
|
|
|
|
|
|
|
|
@JsonProperty(value = "k")
|
|
|
|
private String identityKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class PostCanonicalAddressIdentityMismatchDocument {
|
|
|
|
@JsonProperty(value = "a")
|
|
|
|
private String address;
|
|
|
|
|
|
|
|
@JsonProperty(value = "k")
|
|
|
|
private String identityKey;
|
|
|
|
|
|
|
|
public PostCanonicalAddressIdentityMismatchDocument() {}
|
|
|
|
|
|
|
|
public PostCanonicalAddressIdentityMismatchDocument(String address, String identityKey) {
|
|
|
|
this.address = address;
|
|
|
|
this.identityKey = identityKey;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class PreCanonicalAddressNetworkFailureList {
|
|
|
|
@JsonProperty(value = "l")
|
|
|
|
private List<PreCanonicalAddressNetworkFailureDocument> list;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class PostCanonicalAddressNetworkFailureList {
|
|
|
|
@JsonProperty(value = "l")
|
|
|
|
private List<PostCanonicalAddressNetworkFailureDocument> list;
|
|
|
|
|
|
|
|
public PostCanonicalAddressNetworkFailureList(List<PostCanonicalAddressNetworkFailureDocument> list) {
|
|
|
|
this.list = list;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class PreCanonicalAddressNetworkFailureDocument {
|
|
|
|
@JsonProperty(value = "r")
|
|
|
|
private long recipientId;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class PostCanonicalAddressNetworkFailureDocument {
|
|
|
|
@JsonProperty(value = "a")
|
|
|
|
private String address;
|
|
|
|
|
|
|
|
public PostCanonicalAddressNetworkFailureDocument() {}
|
|
|
|
|
|
|
|
public PostCanonicalAddressNetworkFailureDocument(String address) {
|
|
|
|
this.address = address;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class NumberMigrator {
|
|
|
|
|
|
|
|
private static final String TAG = NumberMigrator.class.getSimpleName();
|
|
|
|
|
|
|
|
private static final Set<String> SHORT_COUNTRIES = new HashSet<String>() {{
|
|
|
|
add("NU");
|
|
|
|
add("TK");
|
|
|
|
add("NC");
|
|
|
|
add("AC");
|
|
|
|
}};
|
|
|
|
|
|
|
|
private final Phonenumber.PhoneNumber localNumber;
|
|
|
|
private final String localNumberString;
|
|
|
|
private final String localCountryCode;
|
|
|
|
|
|
|
|
private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
|
2017-08-01 14:46:38 -07:00
|
|
|
private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]");
|
|
|
|
|
2017-07-26 09:59:15 -07:00
|
|
|
|
|
|
|
public NumberMigrator(String localNumber) {
|
|
|
|
try {
|
|
|
|
this.localNumberString = localNumber;
|
|
|
|
this.localNumber = phoneNumberUtil.parse(localNumber, null);
|
|
|
|
this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(this.localNumber);
|
|
|
|
} catch (NumberParseException e) {
|
|
|
|
throw new AssertionError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public String migrate(@Nullable String number) {
|
2017-08-01 14:46:38 -07:00
|
|
|
if (number == null) return "Unknown";
|
|
|
|
if (number.startsWith("__textsecure_group__!")) return number;
|
2017-08-03 09:24:08 -07:00
|
|
|
if (ALPHA_PATTERN.matcher(number).find()) return number.trim();
|
2017-07-26 09:59:15 -07:00
|
|
|
|
|
|
|
String bareNumber = number.replaceAll("[^0-9+]", "");
|
|
|
|
|
|
|
|
if (bareNumber.length() == 0) {
|
|
|
|
if (TextUtils.isEmpty(number.trim())) return "Unknown";
|
|
|
|
else return number.trim();
|
|
|
|
}
|
|
|
|
|
|
|
|
// libphonenumber doesn't seem to be correct for Germany and Finland
|
|
|
|
if (bareNumber.length() <= 6 && ("DE".equals(localCountryCode) || "FI".equals(localCountryCode) || "SK".equals(localCountryCode))) {
|
|
|
|
return bareNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
// libphonenumber seems incorrect for Russia and a few other countries with 4 digit short codes.
|
|
|
|
if (bareNumber.length() <= 4 && !SHORT_COUNTRIES.contains(localCountryCode)) {
|
|
|
|
return bareNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(bareNumber, localCountryCode);
|
|
|
|
|
|
|
|
if (ShortNumberInfo.getInstance().isPossibleShortNumberForRegion(parsedNumber, localCountryCode)) {
|
|
|
|
return bareNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
|
|
|
|
} catch (NumberParseException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
if (bareNumber.charAt(0) == '+')
|
|
|
|
return bareNumber;
|
|
|
|
|
|
|
|
String localNumberImprecise = localNumberString;
|
|
|
|
|
|
|
|
if (localNumberImprecise.charAt(0) == '+')
|
|
|
|
localNumberImprecise = localNumberImprecise.substring(1);
|
|
|
|
|
2017-07-31 14:04:47 -07:00
|
|
|
if (localNumberImprecise.length() == bareNumber.length() || bareNumber.length() > localNumberImprecise.length())
|
2017-07-26 09:59:15 -07:00
|
|
|
return "+" + number;
|
|
|
|
|
2017-07-31 14:04:47 -07:00
|
|
|
int difference = localNumberImprecise.length() - bareNumber.length();
|
2017-07-26 09:59:15 -07:00
|
|
|
|
2017-07-31 14:04:47 -07:00
|
|
|
return "+" + localNumberImprecise.substring(0, difference) + bareNumber;
|
2017-07-26 09:59:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-12-20 10:20:44 -08:00
|
|
|
}
|