From 70815e61d0e787e18a915f75fef8d0c85ad65a8d Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Fri, 25 Sep 2020 21:11:55 +1000 Subject: [PATCH] Open group avatars. --- .../conversation/ConversationActivity.java | 15 +++++++++++++- .../securesms/database/GroupDatabase.java | 14 ++++++++++++- .../database/helpers/SQLCipherOpenHelper.java | 8 +++++++- .../securesms/loki/api/PublicChatManager.kt | 20 +++++++++++++++---- .../loki/database/LokiAPIDatabase.kt | 19 ++++++++++++++++++ .../loki/views/ProfilePictureView.kt | 9 +++++++++ .../securesms/recipients/Recipient.java | 4 ++++ 7 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index be593f7ea..31f27a97a 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -213,6 +213,7 @@ import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.ExpirationUtil; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.MediaUtil; 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.util.guava.Optional; 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.MentionsManager; import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol; @@ -457,7 +459,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); 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); return Unit.INSTANCE; }); diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index c14aff87d..893cddf9f 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.loki.database.LokiGroupDatabaseProtocol; import java.io.Closeable; import java.io.IOException; @@ -29,7 +30,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -public class GroupDatabase extends Database { +public class GroupDatabase extends Database implements LokiGroupDatabaseProtocol { @SuppressWarnings("unused") private static final String TAG = GroupDatabase.class.getSimpleName(); @@ -240,6 +241,7 @@ public class GroupDatabase extends Database { notifyConversationListListeners(); } + @Override public void updateTitle(String groupId, String title) { ContentValues contentValues = new ContentValues(); contentValues.put(TITLE, title); @@ -254,6 +256,7 @@ public class GroupDatabase extends Database { updateAvatar(groupId, BitmapUtil.toByteArray(avatar)); } + @Override public void updateAvatar(String groupId, byte[] avatar) { long avatarId; @@ -271,6 +274,15 @@ public class GroupDatabase extends Database { 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
members) { Collections.sort(members); diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 21ddb9f1a..b89702e55 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -91,8 +91,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV12 = 33; private static final int lokiV13 = 34; 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 final Context context; @@ -154,6 +155,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampTableCommand()); db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand()); db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand()); + db.execSQL(LokiAPIDatabase.getCreateOpenGroupAvatarCacheCommand()); db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand()); @@ -626,6 +628,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand()); } + if (oldVersion < lokiV15_OPEN_GROUP_AVATARS) { + db.execSQL(LokiAPIDatabase.getCreateOpenGroupAvatarCacheCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index 809573131..018465df4 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.api import android.content.Context import android.database.ContentObserver +import android.graphics.Bitmap import android.text.TextUtils import nl.komponents.kovenant.Promise 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.DatabaseFactory import org.thoughtcrime.securesms.groups.GroupManager +import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.Util +import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatInfo import org.whispersystems.signalservice.loki.api.opengroups.PublicChat class PublicChatManager(private val context: Context) { @@ -56,7 +59,8 @@ class PublicChatManager(private val context: Context) { } public fun addChat(server: String, channel: Long): Promise { - 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 { groupChatAPI.getChannelInfo(channel, server) }.map { @@ -64,12 +68,20 @@ class PublicChatManager(private val context: Context) { } } - public fun addChat(server: String, channel: Long, name: String): PublicChat { - val chat = PublicChat(channel, server, name, true) + public fun addChat(server: String, channel: Long, info: LokiPublicChatInfo): PublicChat { + val chat = PublicChat(channel, server, info.displayName, true) var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) + var avatar: Bitmap? = null // Create the group if we don't have one 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 } DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID) diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index 18fae93b8..0e795aca5 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -71,6 +71,10 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( // 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);" + // 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 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)) } + 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 override fun getDeviceLinks(publicKey: String): Set { return setOf() diff --git a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index 311739d44..ab4b26615 100644 --- a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -68,12 +68,21 @@ class ProfilePictureView : RelativeLayout { return result ?: publicKey } } + fun isOpenGroupWithAvatar(recipient: Recipient): Boolean { + return recipient.isOpenGroupRecipient && + DatabaseFactory.getGroupDatabase(context).hasAvatar(recipient.address.toString()) + } if (recipient.isGroupRecipient) { if ("Session Public Chat" == recipient.name) { publicKey = "" displayName = "" additionalPublicKey = null isRSSFeed = true + } else if (isOpenGroupWithAvatar(recipient)) { + publicKey = recipient.address.toString() + displayName = getUserDisplayName(publicKey) + additionalPublicKey = null + isRSSFeed = false } else { val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf() users.remove(TextSecurePreferences.getLocalNumber(context)) diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 530aa6ecb..c44240a30 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -419,6 +419,10 @@ public class Recipient implements RecipientModifiedListener { return address.isGroup(); } + public boolean isOpenGroupRecipient() { + return address.isOpenGroup(); + } + public boolean isMmsGroupRecipient() { return address.isMmsGroup(); }