2014-01-14 08:26:43 +00:00
|
|
|
package org.thoughtcrime.securesms.database;
|
|
|
|
|
|
|
|
|
|
|
|
import android.content.ContentValues;
|
|
|
|
import android.content.Context;
|
2014-02-24 08:19:54 +00:00
|
|
|
import android.content.Intent;
|
2014-01-14 08:26:43 +00:00
|
|
|
import android.database.Cursor;
|
2014-02-20 23:41:52 +00:00
|
|
|
import android.database.sqlite.SQLiteDatabase;
|
2014-01-14 08:26:43 +00:00
|
|
|
import android.database.sqlite.SQLiteOpenHelper;
|
|
|
|
import android.graphics.Bitmap;
|
2014-02-03 03:38:06 +00:00
|
|
|
import android.util.Log;
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-03 03:38:06 +00:00
|
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
|
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
|
|
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
|
|
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
2014-01-14 08:26:43 +00:00
|
|
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
2014-02-03 03:38:06 +00:00
|
|
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
2014-02-14 23:59:57 +00:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
2014-11-12 19:15:05 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2014-11-03 23:16:04 +00:00
|
|
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-03 03:38:06 +00:00
|
|
|
import java.io.IOException;
|
2014-02-14 01:10:20 +00:00
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.security.SecureRandom;
|
2014-01-14 08:26:43 +00:00
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
public class GroupDatabase extends Database {
|
2014-02-24 08:19:54 +00:00
|
|
|
|
|
|
|
public static final String DATABASE_UPDATE_ACTION = "org.thoughtcrime.securesms.database.GroupDatabase.UPDATE";
|
|
|
|
|
2014-02-24 05:18:08 +00:00
|
|
|
private static final String TAG = GroupDatabase.class.getSimpleName();
|
2014-01-14 08:26:43 +00:00
|
|
|
|
|
|
|
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 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";
|
2014-02-03 03:38:06 +00:00
|
|
|
private static final String AVATAR_RELAY = "avatar_relay";
|
2014-01-14 08:26:43 +00:00
|
|
|
private static final String TIMESTAMP = "timestamp";
|
2014-02-22 18:54:43 +00:00
|
|
|
private static final String ACTIVE = "active";
|
2014-01-14 08:26:43 +00:00
|
|
|
|
|
|
|
public static final String CREATE_TABLE =
|
|
|
|
"CREATE TABLE " + TABLE_NAME +
|
|
|
|
" (" + ID + " INTEGER PRIMARY KEY, " +
|
|
|
|
GROUP_ID + " TEXT, " +
|
|
|
|
TITLE + " TEXT, " +
|
|
|
|
MEMBERS + " TEXT, " +
|
|
|
|
AVATAR + " BLOB, " +
|
|
|
|
AVATAR_ID + " INTEGER, " +
|
|
|
|
AVATAR_KEY + " BLOB, " +
|
|
|
|
AVATAR_CONTENT_TYPE + " TEXT, " +
|
2014-02-03 03:38:06 +00:00
|
|
|
AVATAR_RELAY + " TEXT, " +
|
2014-02-22 18:54:43 +00:00
|
|
|
TIMESTAMP + " INTEGER, " +
|
|
|
|
ACTIVE + " INTEGER DEFAULT 1);";
|
2014-01-14 08:26:43 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-02-22 01:51:25 +00:00
|
|
|
public GroupRecord getGroup(byte[] groupId) {
|
2014-01-14 08:26:43 +00:00
|
|
|
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
|
2014-02-03 03:38:06 +00:00
|
|
|
new String[] {GroupUtil.getEncodedId(groupId)},
|
|
|
|
null, null, null);
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-22 01:51:25 +00:00
|
|
|
Reader reader = new Reader(cursor);
|
|
|
|
GroupRecord record = reader.getNext();
|
|
|
|
|
|
|
|
reader.close();
|
|
|
|
return record;
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
2014-12-14 13:53:56 +00:00
|
|
|
public Reader getGroupsFilteredByTitle(String constraint) {
|
|
|
|
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, TITLE + " LIKE ?",
|
|
|
|
new String[]{"%" + constraint + "%"},
|
|
|
|
null, null, null);
|
|
|
|
|
|
|
|
return new Reader(cursor);
|
|
|
|
}
|
|
|
|
|
2015-06-22 21:49:04 +00:00
|
|
|
public Reader getGroups() {
|
|
|
|
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null);
|
|
|
|
return new Reader(cursor);
|
|
|
|
}
|
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
public Recipients getGroupMembers(byte[] groupId, boolean includeSelf) {
|
|
|
|
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
|
|
|
List<String> members = getCurrentMembers(groupId);
|
2015-06-09 14:37:20 +00:00
|
|
|
List<Recipient> recipients = new LinkedList<>();
|
2014-02-03 03:38:06 +00:00
|
|
|
|
|
|
|
for (String member : members) {
|
2014-02-24 08:19:54 +00:00
|
|
|
if (!includeSelf && member.equals(localNumber))
|
|
|
|
continue;
|
|
|
|
|
2015-03-03 19:44:49 +00:00
|
|
|
recipients.addAll(RecipientFactory.getRecipientsFromString(context, member, false)
|
|
|
|
.getRecipientsList());
|
2014-02-03 03:38:06 +00:00
|
|
|
}
|
|
|
|
|
2015-06-09 14:37:20 +00:00
|
|
|
return RecipientFactory.getRecipientsFor(context, recipients, false);
|
2014-02-03 03:38:06 +00:00
|
|
|
}
|
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
public void create(byte[] groupId, String title, List<String> members,
|
2014-11-03 23:16:04 +00:00
|
|
|
TextSecureAttachmentPointer avatar, String relay)
|
2014-01-14 08:26:43 +00:00
|
|
|
{
|
|
|
|
ContentValues contentValues = new ContentValues();
|
2014-02-03 03:38:06 +00:00
|
|
|
contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId));
|
2014-01-14 08:26:43 +00:00
|
|
|
contentValues.put(TITLE, title);
|
2014-02-24 08:19:54 +00:00
|
|
|
contentValues.put(MEMBERS, Util.join(members, ","));
|
2014-01-14 08:26:43 +00:00
|
|
|
|
|
|
|
if (avatar != null) {
|
|
|
|
contentValues.put(AVATAR_ID, avatar.getId());
|
2014-11-03 23:16:04 +00:00
|
|
|
contentValues.put(AVATAR_KEY, avatar.getKey());
|
2014-01-14 08:26:43 +00:00
|
|
|
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
|
|
|
|
}
|
|
|
|
|
2014-02-03 03:38:06 +00:00
|
|
|
contentValues.put(AVATAR_RELAY, relay);
|
2014-01-14 08:26:43 +00:00
|
|
|
contentValues.put(TIMESTAMP, System.currentTimeMillis());
|
2014-02-22 18:54:43 +00:00
|
|
|
contentValues.put(ACTIVE, 1);
|
2014-01-14 08:26:43 +00:00
|
|
|
|
|
|
|
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
|
|
|
|
}
|
|
|
|
|
2014-11-03 23:16:04 +00:00
|
|
|
public void update(byte[] groupId, String title, TextSecureAttachmentPointer avatar) {
|
2014-01-14 08:26:43 +00:00
|
|
|
ContentValues contentValues = new ContentValues();
|
2014-02-24 05:18:08 +00:00
|
|
|
if (title != null) contentValues.put(TITLE, title);
|
2014-01-14 08:26:43 +00:00
|
|
|
|
|
|
|
if (avatar != null) {
|
|
|
|
contentValues.put(AVATAR_ID, avatar.getId());
|
|
|
|
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
|
2014-11-03 23:16:04 +00:00
|
|
|
contentValues.put(AVATAR_KEY, avatar.getKey());
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
|
2014-02-22 19:29:28 +00:00
|
|
|
GROUP_ID + " = ?",
|
|
|
|
new String[] {GroupUtil.getEncodedId(groupId)});
|
2014-02-24 05:18:08 +00:00
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
RecipientFactory.clearCache();
|
|
|
|
notifyDatabaseListeners();
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
2014-02-20 23:41:52 +00:00
|
|
|
public void updateTitle(byte[] groupId, String title) {
|
|
|
|
ContentValues contentValues = new ContentValues();
|
|
|
|
contentValues.put(TITLE, title);
|
|
|
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
|
|
|
new String[] {GroupUtil.getEncodedId(groupId)});
|
2014-02-24 05:18:08 +00:00
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
RecipientFactory.clearCache();
|
|
|
|
notifyDatabaseListeners();
|
2014-02-20 23:41:52 +00:00
|
|
|
}
|
|
|
|
|
2014-02-03 03:38:06 +00:00
|
|
|
public void updateAvatar(byte[] groupId, Bitmap avatar) {
|
2014-02-24 08:19:54 +00:00
|
|
|
updateAvatar(groupId, BitmapUtil.toByteArray(avatar));
|
2014-02-14 01:10:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void updateAvatar(byte[] groupId, byte[] avatar) {
|
2014-01-14 08:26:43 +00:00
|
|
|
ContentValues contentValues = new ContentValues();
|
2014-02-14 01:10:20 +00:00
|
|
|
contentValues.put(AVATAR, avatar);
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-03 03:38:06 +00:00
|
|
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
|
|
|
new String[] {GroupUtil.getEncodedId(groupId)});
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
RecipientFactory.clearCache();
|
|
|
|
notifyDatabaseListeners();
|
|
|
|
}
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
public void updateMembers(byte[] id, List<String> members) {
|
|
|
|
ContentValues contents = new ContentValues();
|
|
|
|
contents.put(MEMBERS, Util.join(members, ","));
|
|
|
|
contents.put(ACTIVE, 1);
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
|
|
|
|
new String[] {GroupUtil.getEncodedId(id)});
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 + " = ?",
|
2014-02-24 08:19:54 +00:00
|
|
|
new String[] {GroupUtil.getEncodedId(id)});
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private List<String> getCurrentMembers(byte[] id) {
|
|
|
|
Cursor cursor = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS},
|
2014-02-14 23:59:57 +00:00
|
|
|
GROUP_ID + " = ?",
|
|
|
|
new String[] {GroupUtil.getEncodedId(id)},
|
2014-01-14 08:26:43 +00:00
|
|
|
null, null, null);
|
|
|
|
|
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
return Util.split(cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), ",");
|
|
|
|
}
|
|
|
|
|
2014-11-12 19:15:05 +00:00
|
|
|
return new LinkedList<>();
|
2014-01-14 08:26:43 +00:00
|
|
|
} finally {
|
|
|
|
if (cursor != null)
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-22 18:54:43 +00:00
|
|
|
public boolean isActive(byte[] id) {
|
|
|
|
GroupRecord record = getGroup(id);
|
|
|
|
return record != null && record.isActive();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setActive(byte[] id, boolean active) {
|
|
|
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
values.put(ACTIVE, active ? 1 : 0);
|
|
|
|
database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {GroupUtil.getEncodedId(id)});
|
|
|
|
}
|
|
|
|
|
2014-02-20 23:41:52 +00:00
|
|
|
|
2014-02-14 01:10:20 +00:00
|
|
|
public byte[] allocateGroupId() {
|
|
|
|
try {
|
|
|
|
byte[] groupId = new byte[16];
|
|
|
|
SecureRandom.getInstance("SHA1PRNG").nextBytes(groupId);
|
|
|
|
return groupId;
|
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
|
throw new AssertionError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-24 08:19:54 +00:00
|
|
|
private void notifyDatabaseListeners() {
|
|
|
|
Intent intent = new Intent(DATABASE_UPDATE_ACTION);
|
|
|
|
context.sendBroadcast(intent);
|
|
|
|
}
|
2014-01-14 08:26:43 +00:00
|
|
|
|
|
|
|
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)),
|
2014-02-22 18:54:43 +00:00
|
|
|
cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_RELAY)),
|
|
|
|
cursor.getInt(cursor.getColumnIndexOrThrow(ACTIVE)) == 1);
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2014-02-22 18:54:43 +00:00
|
|
|
private final boolean active;
|
2014-01-14 08:26:43 +00:00
|
|
|
|
2014-02-22 19:29:28 +00:00
|
|
|
public GroupRecord(String id, String title, String members, byte[] avatar,
|
2014-01-14 08:26:43 +00:00
|
|
|
long avatarId, byte[] avatarKey, String avatarContentType,
|
2014-02-22 18:54:43 +00:00
|
|
|
String relay, boolean active)
|
2014-01-14 08:26:43 +00:00
|
|
|
{
|
|
|
|
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;
|
2014-02-22 18:54:43 +00:00
|
|
|
this.active = active;
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
2014-02-03 03:38:06 +00:00
|
|
|
public byte[] getId() {
|
|
|
|
try {
|
|
|
|
return GroupUtil.getDecodedId(id);
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
throw new AssertionError(ioe);
|
|
|
|
}
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
|
2014-12-14 13:53:56 +00:00
|
|
|
public String getEncodedId() {
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2014-01-14 08:26:43 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-02-22 18:54:43 +00:00
|
|
|
|
|
|
|
public boolean isActive() {
|
|
|
|
return active;
|
|
|
|
}
|
2014-01-14 08:26:43 +00:00
|
|
|
}
|
|
|
|
}
|