This commit is contained in:
Moxie Marlinspike
2014-01-14 00:26:43 -08:00
parent 4851a555e7
commit 49daa45dca
19 changed files with 1674 additions and 244 deletions

View File

@@ -52,7 +52,8 @@ public class DatabaseFactory {
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 DATABASE_VERSION = 10;
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
private static final int DATABASE_VERSION = 11;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@@ -73,6 +74,7 @@ public class DatabaseFactory {
private final IdentityDatabase identityDatabase;
private final DraftDatabase draftDatabase;
private final PushDatabase pushDatabase;
private final GroupDatabase groupDatabase;
public static DatabaseFactory getInstance(Context context) {
synchronized (lock) {
@@ -138,6 +140,10 @@ public class DatabaseFactory {
return getInstance(context).pushDatabase;
}
public static GroupDatabase getGroupDatabase(Context context) {
return getInstance(context).groupDatabase;
}
private DatabaseFactory(Context context) {
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
this.sms = new SmsDatabase(context, databaseHelper);
@@ -151,6 +157,7 @@ public class DatabaseFactory {
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
this.draftDatabase = new DraftDatabase(context, databaseHelper);
this.pushDatabase = new PushDatabase(context, databaseHelper);
this.groupDatabase = new GroupDatabase(context, databaseHelper);
}
public void reset(Context context) {
@@ -166,6 +173,8 @@ public class DatabaseFactory {
this.mmsSmsDatabase.reset(databaseHelper);
this.identityDatabase.reset(databaseHelper);
this.draftDatabase.reset(databaseHelper);
this.pushDatabase.reset(databaseHelper);
this.groupDatabase.reset(databaseHelper);
old.close();
this.address.reset(context);
@@ -432,6 +441,7 @@ public class DatabaseFactory {
db.execSQL(IdentityDatabase.CREATE_TABLE);
db.execSQL(DraftDatabase.CREATE_TABLE);
db.execSQL(PushDatabase.CREATE_TABLE);
db.execSQL(GroupDatabase.CREATE_TABLE);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
@@ -439,6 +449,7 @@ public class DatabaseFactory {
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
executeStatements(db, MmsAddressDatabase.CREATE_INDEXS);
executeStatements(db, DraftDatabase.CREATE_INDEXS);
executeStatements(db, GroupDatabase.CREATE_INDEXS);
}
@Override
@@ -630,6 +641,11 @@ public class DatabaseFactory {
db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON part (pending_push);");
}
if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) {
db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, owner TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, timestamp INTEGER);");
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);");
}
db.setTransactionSuccessful();
db.endTransaction();
}

View File

@@ -0,0 +1,241 @@
package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
import java.util.LinkedList;
import java.util.List;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
public class GroupDatabase extends Database {
private static final String TABLE_NAME = "groups";
private static final String ID = "_id";
private static final String GROUP_ID = "group_id";
private static final String OWNER = "owner";
private static final String TITLE = "title";
private static final String MEMBERS = "members";
private static final String AVATAR = "avatar";
private static final String AVATAR_ID = "avatar_id";
private static final String AVATAR_KEY = "avatar_key";
private static final String AVATAR_CONTENT_TYPE = "avatar_content_type";
private static final String RELAY = "relay";
private static final String TIMESTAMP = "timestamp";
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME +
" (" + ID + " INTEGER PRIMARY KEY, " +
GROUP_ID + " TEXT, " +
OWNER + " TEXT, " +
TITLE + " TEXT, " +
MEMBERS + " TEXT, " +
AVATAR + " BLOB, " +
AVATAR_ID + " INTEGER, " +
AVATAR_KEY + " BLOB, " +
AVATAR_CONTENT_TYPE + " TEXT, " +
TIMESTAMP + " INTEGER);";
public static final String[] CREATE_INDEXS = {
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
};
public GroupDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public Reader getGroup(String groupId) {
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
new String[] {groupId}, null, null, null);
return new Reader(cursor);
}
public void create(byte[] groupId, String owner, String title,
List<String> members, AttachmentPointer avatar,
String relay)
{
ContentValues contentValues = new ContentValues();
contentValues.put(GROUP_ID, Hex.toString(groupId));
contentValues.put(OWNER, owner);
contentValues.put(TITLE, title);
contentValues.put(MEMBERS, Util.join(members, ","));
if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId());
contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray());
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
}
contentValues.put(RELAY, relay);
contentValues.put(TIMESTAMP, System.currentTimeMillis());
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
}
public void update(byte[] groupId, String source, String title, AttachmentPointer avatar) {
ContentValues contentValues = new ContentValues();
if (title != null) contentValues.put(TITLE, title);
if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId());
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray());
}
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
GROUP_ID + " = ? AND " + OWNER + " = ?",
new String[] {Hex.toString(groupId), source});
}
public void updateAvatar(String groupId, Bitmap avatar) {
ContentValues contentValues = new ContentValues();
contentValues.put(AVATAR, BitmapUtil.toByteArray(avatar));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupId});
}
public void add(byte[] id, String source, List<String> members) {
List<String> currentMembers = getCurrentMembers(id);
for (String currentMember : currentMembers) {
if (currentMember.equals(source)) {
List<String> concatenatedMembers = new LinkedList<String>(currentMembers);
concatenatedMembers.addAll(members);
ContentValues contents = new ContentValues();
contents.put(MEMBERS, Util.join(concatenatedMembers, ","));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[] {Hex.toString(id)});
}
}
}
public void remove(byte[] id, String source) {
List<String> currentMembers = getCurrentMembers(id);
currentMembers.remove(source);
ContentValues contents = new ContentValues();
contents.put(MEMBERS, Util.join(currentMembers, ","));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[]{Hex.toString(id)});
}
private List<String> getCurrentMembers(byte[] id) {
Cursor cursor = null;
try {
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS},
GROUP_ID + " = ?", new String[] {Hex.toString(id)},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return Util.split(cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), ",");
}
return new LinkedList<String>();
} finally {
if (cursor != null)
cursor.close();
}
}
public static class Reader {
private final Cursor cursor;
public Reader(Cursor cursor) {
this.cursor = cursor;
}
public GroupRecord getNext() {
if (cursor == null || !cursor.moveToNext()) {
return null;
}
return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)),
cursor.getString(cursor.getColumnIndexOrThrow(TITLE)),
cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)),
cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR)),
cursor.getLong(cursor.getColumnIndexOrThrow(AVATAR_ID)),
cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR_KEY)),
cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_CONTENT_TYPE)),
cursor.getString(cursor.getColumnIndexOrThrow(RELAY)));
}
public void close() {
if (this.cursor != null)
this.cursor.close();
}
}
public static class GroupRecord {
private final String id;
private final String title;
private final List<String> members;
private final byte[] avatar;
private final long avatarId;
private final byte[] avatarKey;
private final String avatarContentType;
private final String relay;
public GroupRecord(String id, String title, String members, byte[] avatar,
long avatarId, byte[] avatarKey, String avatarContentType,
String relay)
{
this.id = id;
this.title = title;
this.members = Util.split(members, ",");
this.avatar = avatar;
this.avatarId = avatarId;
this.avatarKey = avatarKey;
this.avatarContentType = avatarContentType;
this.relay = relay;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public List<String> getMembers() {
return members;
}
public byte[] getAvatar() {
return avatar;
}
public long getAvatarId() {
return avatarId;
}
public byte[] getAvatarKey() {
return avatarKey;
}
public String getAvatarContentType() {
return avatarContentType;
}
public String getRelay() {
return relay;
}
}
}

View File

@@ -179,20 +179,38 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
}
private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException {
if (retrieved.getGroupId() != null) {
return DatabaseFactory.getThreadDatabase(context).getThreadIdForGroup(retrieved.getGroupId());
}
try {
PduHeaders headers = retrieved.getPduHeaders();
Set<String> group = new HashSet<String>();
EncodedStringValue encodedFrom = headers.getEncodedStringValue(PduHeaders.FROM);
EncodedStringValue encodedFrom = headers.getEncodedStringValue(PduHeaders.FROM);
EncodedStringValue[] encodedCcList = headers.getEncodedStringValues(PduHeaders.CC);
EncodedStringValue[] encodedToList = headers.getEncodedStringValues(PduHeaders.TO);
group.add(new String(encodedFrom.getTextString(), CharacterSets.MIMENAME_ISO_8859_1));
EncodedStringValue[] encodedCcList = headers.getEncodedStringValues(PduHeaders.CC);
if (encodedCcList != null) {
for (EncodedStringValue encodedCc : encodedCcList) {
group.add(new String(encodedCc.getTextString(), CharacterSets.MIMENAME_ISO_8859_1));
}
}
if (encodedToList != null) {
String localNumber = Util.getDeviceE164Number(context);
for (EncodedStringValue encodedTo : encodedToList) {
String to = new String(encodedTo.getTextString(), CharacterSets.MIMENAME_ISO_8859_1);
if (!localNumber.equals(to)) {
group.add(to);
}
}
}
String recipientsList = Util.join(group, ",");
Recipients recipients = RecipientFactory.getRecipientsFromString(context, recipientsList, false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);

View File

@@ -18,12 +18,11 @@ public class PushDatabase extends Database {
public static final String ID = "_id";
public static final String TYPE = "type";
public static final String SOURCE = "source";
public static final String DESTINATIONS = "destinations";
public static final String BODY = "body";
public static final String TIMESTAMP = "timestamp";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
TYPE + " INTEGER, " + SOURCE + " TEXT, " + DESTINATIONS + " TEXT, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);";
TYPE + " INTEGER, " + SOURCE + " TEXT, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);";
public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
@@ -33,7 +32,6 @@ public class PushDatabase extends Database {
ContentValues values = new ContentValues();
values.put(TYPE, message.getType());
values.put(SOURCE, message.getSource());
values.put(DESTINATIONS, Util.join(message.getDestinations(), ","));
values.put(BODY, Base64.encodeBytes(message.getBody()));
values.put(TIMESTAMP, message.getTimestampMillis());
@@ -66,11 +64,10 @@ public class PushDatabase extends Database {
int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE));
List<String> destinations = Util.split(cursor.getString(cursor.getColumnIndexOrThrow(DESTINATIONS)), ",");
byte[] body = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(BODY)));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
return new IncomingPushMessage(type, source, destinations, body, timestamp);
return new IncomingPushMessage(type, source, body, timestamp);
} catch (IOException e) {
throw new AssertionError(e);
}

View File

@@ -259,10 +259,15 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
Recipient recipient = new Recipient(null, message.getSender(), null, null);
Recipients recipients = new Recipients(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
String groupId = message.getGroupId();
boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
message.isSecureMessage() || message.isKeyExchange();
long threadId;
if (groupId == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdForGroup(groupId);
ContentValues values = new ContentValues(6);
values.put(ADDRESS, message.getSender());
values.put(DATE_RECEIVED, System.currentTimeMillis());

View File

@@ -101,6 +101,17 @@ public class ThreadDatabase extends Database {
return sb.toString();
}
private long createThreadForGroup(String group) {
long date = System.currentTimeMillis();
ContentValues values = new ContentValues();
values.put(DATE, date - date % 1000);
values.put(RECIPIENT_IDS, group);
values.put(MESSAGE_COUNT, 0);
return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values);
}
private long createThreadForRecipients(String recipients, int recipientCount, int distributionType) {
ContentValues contentValues = new ContentValues(4);
long date = System.currentTimeMillis();
@@ -325,7 +336,7 @@ public class ThreadDatabase extends Database {
}
public long getThreadIdFor(Recipients recipients) {
return getThreadIdFor(recipients, 0);
return getThreadIdFor(recipients, DistributionTypes.DEFAULT);
}
public long getThreadIdFor(Recipients recipients, int distributionType) {
@@ -349,6 +360,26 @@ public class ThreadDatabase extends Database {
}
}
public long getThreadIdForGroup(String groupId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String where = RECIPIENT_IDS + " = ?";
String[] recipientsArg = new String[] {groupId};
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME, new String[]{ID}, where, recipientsArg, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(ID));
} else {
return createThreadForGroup(groupId);
}
} finally {
if (cursor != null)
cursor.close();
}
}
public Recipients getRecipientsForThreadId(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;