Open group avatars.

This commit is contained in:
Anton Chekulaev 2020-09-25 21:11:55 +10:00
parent 2c173a963d
commit 70815e61d0
7 changed files with 82 additions and 7 deletions

View File

@ -213,6 +213,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ServiceUtil;
@ -227,6 +228,7 @@ import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat; import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
import org.whispersystems.signalservice.loki.protocol.mentions.Mention; import org.whispersystems.signalservice.loki.protocol.mentions.Mention;
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager; import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol; import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
@ -457,7 +459,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) { if (publicChat != null) {
ApplicationContext.getInstance(this).getPublicChatAPI().getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(displayName -> { PublicChatAPI publicChatAPI = ApplicationContext.getInstance(this).getPublicChatAPI();
publicChatAPI.getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(info -> {
String groupId = GroupUtil.getEncodedOpenGroupId(publicChat.getId().getBytes());
publicChatAPI.updateOpenGroupProfileIfNeeded(
publicChat.getChannel(),
publicChat.getServer(),
groupId,
info,
DatabaseFactory.getGroupDatabase(this),
false);
runOnUiThread(ConversationActivity.this::updateSubtitleTextView); runOnUiThread(ConversationActivity.this::updateSubtitleTextView);
return Unit.INSTANCE; return Unit.INSTANCE;
}); });

View File

@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.loki.database.LokiGroupDatabaseProtocol;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@ -29,7 +30,7 @@ import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public class GroupDatabase extends Database { public class GroupDatabase extends Database implements LokiGroupDatabaseProtocol {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = GroupDatabase.class.getSimpleName(); private static final String TAG = GroupDatabase.class.getSimpleName();
@ -240,6 +241,7 @@ public class GroupDatabase extends Database {
notifyConversationListListeners(); notifyConversationListListeners();
} }
@Override
public void updateTitle(String groupId, String title) { public void updateTitle(String groupId, String title) {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(TITLE, title); contentValues.put(TITLE, title);
@ -254,6 +256,7 @@ public class GroupDatabase extends Database {
updateAvatar(groupId, BitmapUtil.toByteArray(avatar)); updateAvatar(groupId, BitmapUtil.toByteArray(avatar));
} }
@Override
public void updateAvatar(String groupId, byte[] avatar) { public void updateAvatar(String groupId, byte[] avatar) {
long avatarId; long avatarId;
@ -271,6 +274,15 @@ public class GroupDatabase extends Database {
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId)); Recipient.applyCached(Address.fromSerialized(groupId), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId));
} }
public boolean hasAvatar(String groupId) {
try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(
"SELECT COUNT("+ID+") FROM "+TABLE_NAME+" WHERE "+GROUP_ID+" == ? AND "+AVATAR+" NOT NULL",
new String[]{groupId})) {
cursor.moveToFirst();
return cursor.getInt(0) > 0;
}
}
public void updateMembers(String groupId, List<Address> members) { public void updateMembers(String groupId, List<Address> members) {
Collections.sort(members); Collections.sort(members);

View File

@ -91,8 +91,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV12 = 33; private static final int lokiV12 = 33;
private static final int lokiV13 = 34; private static final int lokiV13 = 34;
private static final int lokiV14_BACKUP_FILES = 35; private static final int lokiV14_BACKUP_FILES = 35;
private static final int lokiV15_OPEN_GROUP_AVATARS = 36;
private static final int DATABASE_VERSION = lokiV14_BACKUP_FILES; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final int DATABASE_VERSION = lokiV15_OPEN_GROUP_AVATARS; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -154,6 +155,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampTableCommand()); db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampTableCommand());
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand()); db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand());
db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand()); db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand());
db.execSQL(LokiAPIDatabase.getCreateOpenGroupAvatarCacheCommand());
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
@ -626,6 +628,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand()); db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
} }
if (oldVersion < lokiV15_OPEN_GROUP_AVATARS) {
db.execSQL(LokiAPIDatabase.getCreateOpenGroupAvatarCacheCommand());
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.api
import android.content.Context import android.content.Context
import android.database.ContentObserver import android.database.ContentObserver
import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
@ -10,8 +11,10 @@ import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatInfo
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
class PublicChatManager(private val context: Context) { class PublicChatManager(private val context: Context) {
@ -56,7 +59,8 @@ class PublicChatManager(private val context: Context) {
} }
public fun addChat(server: String, channel: Long): Promise<PublicChat, Exception> { public fun addChat(server: String, channel: Long): Promise<PublicChat, Exception> {
val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI ?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!")) val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI
?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
return groupChatAPI.getAuthToken(server).bind { return groupChatAPI.getAuthToken(server).bind {
groupChatAPI.getChannelInfo(channel, server) groupChatAPI.getChannelInfo(channel, server)
}.map { }.map {
@ -64,12 +68,20 @@ class PublicChatManager(private val context: Context) {
} }
} }
public fun addChat(server: String, channel: Long, name: String): PublicChat { public fun addChat(server: String, channel: Long, info: LokiPublicChatInfo): PublicChat {
val chat = PublicChat(channel, server, name, true) val chat = PublicChat(channel, server, info.displayName, true)
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
var avatar: Bitmap? = null
// Create the group if we don't have one // Create the group if we don't have one
if (threadID < 0) { if (threadID < 0) {
val result = GroupManager.createOpenGroup(chat.id, context, null, chat.displayName) if (!info.profilePictureURL.isEmpty()) {
val avatarBytes = ApplicationContext.getInstance(context).publicChatAPI
?.downloadOpenGroupAvatar(server, info.profilePictureURL)
avatar = BitmapUtil.fromByteArray(avatarBytes)
}
// FIXME: If updating the avatar here, there can be a memory issue if a public chat message contains some attachment.
// The error message is "Failed to execute task in background: Canvas: trying to use a recycled bitmap android.graphics.Bitmap"
val result = GroupManager.createOpenGroup(chat.id, context, avatar, chat.displayName)
threadID = result.threadId threadID = result.threadId
} }
DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID) DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID)

View File

@ -71,6 +71,10 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
// Open group public keys // Open group public keys
private val openGroupPublicKeyTable = "open_group_public_keys" private val openGroupPublicKeyTable = "open_group_public_keys"
@JvmStatic val createOpenGroupPublicKeyTableCommand = "CREATE TABLE $openGroupPublicKeyTable ($server STRING PRIMARY KEY, $publicKey INTEGER DEFAULT 0);" @JvmStatic val createOpenGroupPublicKeyTableCommand = "CREATE TABLE $openGroupPublicKeyTable ($server STRING PRIMARY KEY, $publicKey INTEGER DEFAULT 0);"
// Open group avatar cache
private val openGroupAvatarCacheTable = "open_group_avatar_cache"
private val openGroupAvatar = "open_group_avatar"
@JvmStatic val createOpenGroupAvatarCacheCommand = "CREATE TABLE $openGroupAvatarCacheTable ($publicChatID STRING PRIMARY KEY, $openGroupAvatar TEXT NULLABLE DEFAULT NULL);"
// region Deprecated // region Deprecated
private val deviceLinkCache = "loki_pairing_authorisation_cache" private val deviceLinkCache = "loki_pairing_authorisation_cache"
@ -343,6 +347,21 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.insertOrUpdate(openGroupPublicKeyTable, row, "${LokiAPIDatabase.server} = ?", wrap(server)) database.insertOrUpdate(openGroupPublicKeyTable, row, "${LokiAPIDatabase.server} = ?", wrap(server))
} }
override fun getOpenGroupAvatarURL(group: Long, server: String): String? {
val database = databaseHelper.readableDatabase
val index = "$server.$group"
return database.get(openGroupAvatarCacheTable, "$publicChatID = ?", wrap(index)) { cursor ->
cursor.getString(openGroupAvatar)
}?.toString()
}
override fun setOpenGroupAvatarURL(url: String, group: Long, server: String) {
val database = databaseHelper.writableDatabase
val index = "$server.$group"
val row = wrap(mapOf(publicChatID to index, openGroupAvatar to url))
database.insertOrUpdate(openGroupAvatarCacheTable, row, "$publicChatID = ?", wrap(index))
}
// region Deprecated // region Deprecated
override fun getDeviceLinks(publicKey: String): Set<DeviceLink> { override fun getDeviceLinks(publicKey: String): Set<DeviceLink> {
return setOf() return setOf()

View File

@ -68,12 +68,21 @@ class ProfilePictureView : RelativeLayout {
return result ?: publicKey return result ?: publicKey
} }
} }
fun isOpenGroupWithAvatar(recipient: Recipient): Boolean {
return recipient.isOpenGroupRecipient &&
DatabaseFactory.getGroupDatabase(context).hasAvatar(recipient.address.toString())
}
if (recipient.isGroupRecipient) { if (recipient.isGroupRecipient) {
if ("Session Public Chat" == recipient.name) { if ("Session Public Chat" == recipient.name) {
publicKey = "" publicKey = ""
displayName = "" displayName = ""
additionalPublicKey = null additionalPublicKey = null
isRSSFeed = true isRSSFeed = true
} else if (isOpenGroupWithAvatar(recipient)) {
publicKey = recipient.address.toString()
displayName = getUserDisplayName(publicKey)
additionalPublicKey = null
isRSSFeed = false
} else { } else {
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf() val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf()
users.remove(TextSecurePreferences.getLocalNumber(context)) users.remove(TextSecurePreferences.getLocalNumber(context))

View File

@ -419,6 +419,10 @@ public class Recipient implements RecipientModifiedListener {
return address.isGroup(); return address.isGroup();
} }
public boolean isOpenGroupRecipient() {
return address.isOpenGroup();
}
public boolean isMmsGroupRecipient() { public boolean isMmsGroupRecipient() {
return address.isMmsGroup(); return address.isMmsGroup();
} }