Add avatar picker and defaults.

This commit is contained in:
Alex Hart
2021-07-20 13:08:52 -03:00
committed by Greyson Parrelli
parent 0093e1d3eb
commit ed23c3fe7c
133 changed files with 4935 additions and 859 deletions

View File

@@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -70,6 +71,7 @@ public class DatabaseFactory {
private final ChatColorsDatabase chatColorsDatabase;
private final EmojiSearchDatabase emojiSearchDatabase;
private final MessageSendLogDatabase messageSendLogDatabase;
private final AvatarPickerDatabase avatarPickerDatabase;
public static DatabaseFactory getInstance(Context context) {
if (instance == null) {
@@ -200,6 +202,10 @@ public class DatabaseFactory {
return getInstance(context).messageSendLogDatabase;
}
public static AvatarPickerDatabase getAvatarPickerDatabase(Context context) {
return getInstance(context).avatarPickerDatabase;
}
public static SQLiteDatabase getBackupDatabase(Context context) {
return getInstance(context).databaseHelper.getReadableDatabase().getSqlCipherDatabase();
}
@@ -259,8 +265,9 @@ public class DatabaseFactory {
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
this.paymentDatabase = new PaymentDatabase(context, databaseHelper);
this.chatColorsDatabase = new ChatColorsDatabase(context, databaseHelper);
this.emojiSearchDatabase = new EmojiSearchDatabase(context, databaseHelper);
this.messageSendLogDatabase = new MessageSendLogDatabase(context, databaseHelper);
this.emojiSearchDatabase = new EmojiSearchDatabase(context, databaseHelper);
this.messageSendLogDatabase = new MessageSendLogDatabase(context, databaseHelper);
this.avatarPickerDatabase = new AvatarPickerDatabase(context, databaseHelper);
}
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,

View File

@@ -57,6 +57,7 @@ import org.thoughtcrime.securesms.database.SqlCipherErrorHandler;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.UnknownStorageIdDatabase;
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
@@ -207,8 +208,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int THREAD_AUTOINCREMENT = 108;
private static final int MMS_AUTOINCREMENT = 109;
private static final int ABANDONED_ATTACHMENT_CLEANUP = 110;
private static final int AVATAR_PICKER = 111;
private static final int DATABASE_VERSION = 110;
private static final int DATABASE_VERSION = 111;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@@ -245,6 +247,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL(PaymentDatabase.CREATE_TABLE);
db.execSQL(ChatColorsDatabase.CREATE_TABLE);
db.execSQL(EmojiSearchDatabase.CREATE_TABLE);
db.execSQL(AvatarPickerDatabase.CREATE_TABLE);
executeStatements(db, SearchDatabase.CREATE_TABLE);
executeStatements(db, RemappedRecordsDatabase.CREATE_TABLE);
executeStatements(db, MessageSendLogDatabase.CREATE_TABLE);
@@ -1934,6 +1937,24 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.delete("part", "mid != -8675309 AND mid NOT IN (SELECT _id FROM mms)", null);
}
if (oldVersion < AVATAR_PICKER) {
db.execSQL("CREATE TABLE avatar_picker (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"last_used INTEGER DEFAULT 0, " +
"group_id TEXT DEFAULT NULL, " +
"avatar BLOB NOT NULL)");
try (Cursor cursor = db.query("recipient", new String[] { "_id" }, "color IS NULL", null, null, null, null)) {
while (cursor.moveToNext()) {
long id = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
ContentValues values = new ContentValues(1);
values.put("color", AvatarColor.random().serialize());
db.update("recipient", values, "_id = ?", new String[] { String.valueOf(id) });
}
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

View File

@@ -0,0 +1,185 @@
package org.thoughtcrime.securesms.database.model
import android.content.ContentValues
import android.content.Context
import android.net.Uri
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.databaseprotos.CustomAvatar
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.util.CursorUtil
import org.thoughtcrime.securesms.util.SqlUtil
/**
* Database which manages the record keeping for custom created avatars.
*/
class AvatarPickerDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Database(context, databaseHelper) {
companion object {
private const val TABLE_NAME = "avatar_picker"
private const val ID = "_id"
private const val LAST_USED = "last_used"
private const val GROUP_ID = "group_id"
private const val AVATAR = "avatar"
//language=sql
@JvmField
val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
$LAST_USED INTEGER DEFAULT 0,
$GROUP_ID TEXT DEFAULT NULL,
$AVATAR BLOB NOT NULL
)
""".trimIndent()
}
fun saveAvatarForSelf(avatar: Avatar): Avatar {
return saveAvatar(avatar, null)
}
fun saveAvatarForGroup(avatar: Avatar, groupId: GroupId): Avatar {
return saveAvatar(avatar, groupId)
}
fun markUsage(avatar: Avatar) {
val databaseId = avatar.databaseId
if (databaseId !is Avatar.DatabaseId.Saved) {
throw IllegalArgumentException("Must save this avatar before trying to mark usage.")
}
val db = databaseHelper.writableDatabase
val where = ID_WHERE
val args = SqlUtil.buildArgs(databaseId.id)
val values = ContentValues(1)
values.put(LAST_USED, System.currentTimeMillis())
db.update(TABLE_NAME, values, where, args)
}
fun update(avatar: Avatar) {
val databaseId = avatar.databaseId
if (databaseId !is Avatar.DatabaseId.Saved) {
throw IllegalArgumentException("Cannot update an unsaved avatar")
}
val db = databaseHelper.writableDatabase
val where = ID_WHERE
val values = ContentValues(1)
values.put(AVATAR, avatar.toProto().toByteArray())
db.update(TABLE_NAME, values, where, SqlUtil.buildArgs(databaseId.id))
}
fun deleteAvatar(avatar: Avatar) {
val databaseId = avatar.databaseId
if (databaseId !is Avatar.DatabaseId.Saved) {
throw IllegalArgumentException("Cannot delete an unsaved avatar.")
}
val db = databaseHelper.writableDatabase
val where = ID_WHERE
val args = SqlUtil.buildArgs(databaseId.id)
db.delete(TABLE_NAME, where, args)
}
private fun saveAvatar(avatar: Avatar, groupId: GroupId?): Avatar {
val db = databaseHelper.writableDatabase
val databaseId = avatar.databaseId
if (databaseId is Avatar.DatabaseId.DoNotPersist) {
throw IllegalArgumentException("Cannot persist this avatar")
}
if (databaseId is Avatar.DatabaseId.Saved) {
val values = ContentValues(2)
values.put(AVATAR, avatar.toProto().toByteArray())
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(databaseId.id))
return avatar
} else {
val values = ContentValues(4)
values.put(AVATAR, avatar.toProto().toByteArray())
if (groupId != null) {
values.put(GROUP_ID, groupId.toString())
}
val id = db.insert(TABLE_NAME, null, values)
if (id == -1L) {
throw AssertionError("Failed to save avatar")
}
return avatar.withDatabaseId(Avatar.DatabaseId.Saved(id))
}
}
fun getAllAvatars(): List<Avatar> {
val db = databaseHelper.readableDatabase
val results = mutableListOf<Avatar>()
db.query(TABLE_NAME, SqlUtil.buildArgs(ID, AVATAR), null, null, null, null, null)?.use {
while (it.moveToNext()) {
val id = CursorUtil.requireLong(it, ID)
val blob = CursorUtil.requireBlob(it, AVATAR)
val proto = CustomAvatar.parseFrom(blob)
results.add(proto.toAvatar(id))
}
}
return results
}
fun getAvatarsForSelf(): List<Avatar> {
return getAvatars(null)
}
fun getAvatarsForGroup(groupId: GroupId): List<Avatar> {
return getAvatars(groupId)
}
private fun getAvatars(groupId: GroupId?): List<Avatar> {
val db = databaseHelper.readableDatabase
val orderBy = "$LAST_USED DESC"
val results = mutableListOf<Avatar>()
val (where, args) = if (groupId == null) {
Pair("$GROUP_ID is NULL", null)
} else {
Pair("$GROUP_ID = ?", SqlUtil.buildArgs(groupId))
}
db.query(TABLE_NAME, SqlUtil.buildArgs(ID, AVATAR), where, args, null, null, orderBy)?.use {
while (it.moveToNext()) {
val id = CursorUtil.requireLong(it, ID)
val blob = CursorUtil.requireBlob(it, AVATAR)
val proto = CustomAvatar.parseFrom(blob)
results.add(proto.toAvatar(id))
}
}
return results
}
private fun Avatar.toProto(): CustomAvatar {
return when (this) {
is Avatar.Photo -> CustomAvatar.newBuilder().setPhoto(CustomAvatar.Photo.newBuilder().setUri(this.uri.toString())).build()
is Avatar.Text -> CustomAvatar.newBuilder().setText(CustomAvatar.Text.newBuilder().setText(this.text).setColors(this.color.code)).build()
is Avatar.Vector -> CustomAvatar.newBuilder().setVector(CustomAvatar.Vector.newBuilder().setKey(this.key).setColors(this.color.code)).build()
else -> throw AssertionError()
}
}
private fun CustomAvatar.toAvatar(id: Long): Avatar {
return when {
hasPhoto() -> Avatar.Photo(Uri.parse(photo.uri), photo.size, Avatar.DatabaseId.Saved(id))
hasText() -> Avatar.Text(text.text, Avatars.colorMap[text.colors] ?: Avatars.colors[0], Avatar.DatabaseId.Saved(id))
hasVector() -> Avatar.Vector(vector.key, Avatars.colorMap[vector.colors] ?: Avatars.colors[0], Avatar.DatabaseId.Saved(id))
else -> throw AssertionError()
}
}
}