From 70815e61d0e787e18a915f75fef8d0c85ad65a8d Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Fri, 25 Sep 2020 21:11:55 +1000 Subject: [PATCH 01/14] 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 be593f7ea5..31f27a97a2 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 c14aff87d5..893cddf9f0 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 21ddb9f1a0..b89702e552 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 8095731318..018465df43 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 18fae93b87..0e795aca57 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 311739d44d..ab4b266158 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 530aa6ecb1..c44240a30b 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(); } From 2920e3e52835512dd03d28b7aa75504f10c81b10 Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Mon, 28 Sep 2020 19:56:40 +1000 Subject: [PATCH 02/14] Open group avatar DB record cleanup on deletion. General cleanup. --- .../securesms/loki/activities/HomeActivity.kt | 24 ++++----- .../loki/database/LokiAPIDatabase.kt | 8 ++- .../loki/views/ProfilePictureView.kt | 50 ++++++++----------- 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 5c81027f7d..ee35b0a283 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -340,20 +340,18 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe val threadID = thread.threadId val recipient = thread.recipient val threadDB = DatabaseFactory.getThreadDatabase(this) - val deleteThread = object : Runnable { - - override fun run() { - AsyncTask.execute { - val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID) - if (publicChat != null) { - val apiDB = DatabaseFactory.getLokiAPIDatabase(this@HomeActivity) - apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server) - apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server) - ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server) - } - threadDB.deleteConversation(threadID) - ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity) + val deleteThread = Runnable { + AsyncTask.execute { + val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID) + if (publicChat != null) { + val apiDB = DatabaseFactory.getLokiAPIDatabase(this@HomeActivity) + apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server) + apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server) + apiDB.clearOpenGroupAvatarURL(publicChat.channel, publicChat.server) + ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server) } + threadDB.deleteConversation(threadID) + ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity) } } val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index 0e795aca57..0d81d464a5 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -355,13 +355,19 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( }?.toString() } - override fun setOpenGroupAvatarURL(url: String, group: Long, server: String) { + override fun setOpenGroupAvatarURL(group: Long, server: String, url: 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)) } + fun clearOpenGroupAvatarURL(group: Long, server: String): Boolean { + val database = databaseHelper.writableDatabase + val index = "$server.$group" + return database.delete(openGroupAvatarCacheTable, "$publicChatID == ?", arrayOf(index)) > 0 + } + // 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 ab4b266158..a7dd90a5fd 100644 --- a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -72,37 +72,27 @@ class ProfilePictureView : RelativeLayout { 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)) - val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) - if (masterPublicKey != null) { - users.remove(masterPublicKey) - } - val randomUsers = users.sorted().toMutableList() // Sort to provide a level of stability - if (users.count() == 1) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - randomUsers.add(0, userPublicKey) // Ensure the current user is at the back visually - } - val pk = randomUsers.getOrNull(0) ?: "" - publicKey = pk - displayName = getUserDisplayName(pk) - val apk = randomUsers.getOrNull(1) ?: "" - additionalPublicKey = apk - additionalDisplayName = getUserDisplayName(apk) - isRSSFeed = recipient.name == "Loki News" || recipient.name == "Session Updates" + if (recipient.isGroupRecipient && !isOpenGroupWithAvatar(recipient)) { + val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf() + users.remove(TextSecurePreferences.getLocalNumber(context)) + val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) + if (masterPublicKey != null) { + users.remove(masterPublicKey) } + val randomUsers = users.sorted().toMutableList() // Sort to provide a level of stability + if (users.count() == 1) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context) + randomUsers.add(0, userPublicKey) // Ensure the current user is at the back visually + } + val pk = randomUsers.getOrNull(0) ?: "" + publicKey = pk + displayName = getUserDisplayName(pk) + val apk = randomUsers.getOrNull(1) ?: "" + additionalPublicKey = apk + additionalDisplayName = getUserDisplayName(apk) + isRSSFeed = recipient.name == "Loki News" || + recipient.name == "Session Updates" || + recipient.name == "Session Public Chat" } else { publicKey = recipient.address.toString() displayName = getUserDisplayName(publicKey) From 7dffacf9577c32065b3b4765a933b4e805d4f8b7 Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Wed, 30 Sep 2020 17:33:53 +1000 Subject: [PATCH 03/14] Avatar check optimization. --- src/org/thoughtcrime/securesms/database/GroupDatabase.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index 893cddf9f0..95db6f1b99 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -276,10 +276,10 @@ public class GroupDatabase extends Database implements LokiGroupDatabaseProtocol public boolean hasAvatar(String groupId) { try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery( - "SELECT COUNT("+ID+") FROM "+TABLE_NAME+" WHERE "+GROUP_ID+" == ? AND "+AVATAR+" NOT NULL", + "SELECT EXISTS(SELECT 1 FROM "+TABLE_NAME+" WHERE "+GROUP_ID+" == ? AND "+AVATAR+" NOT NULL LIMIT 1)", new String[]{groupId})) { cursor.moveToFirst(); - return cursor.getInt(0) > 0; + return cursor.getInt(0) == 1; } } From f620d1cdb9b92edb62d046e2916c0ec5635e117f Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Thu, 1 Oct 2020 21:15:22 +1000 Subject: [PATCH 04/14] Use a Bitmap instead of a BitmapDrawable for open group attachment notification icon. --- .../securesms/loki/api/PublicChatManager.kt | 2 -- .../securesms/loki/views/ProfilePictureView.kt | 3 +-- .../SingleRecipientNotificationBuilder.java | 18 +++++++++++------- .../securesms/recipients/Recipient.java | 5 +++++ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index 018465df43..5cddbe2b6b 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -79,8 +79,6 @@ class PublicChatManager(private val context: Context) { ?.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 } diff --git a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index a7dd90a5fd..f8b0d5b6a3 100644 --- a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -69,8 +69,7 @@ class ProfilePictureView : RelativeLayout { } } fun isOpenGroupWithAvatar(recipient: Recipient): Boolean { - return recipient.isOpenGroupRecipient && - DatabaseFactory.getGroupDatabase(context).hasAvatar(recipient.address.toString()) + return recipient.isOpenGroupRecipient && recipient.groupAvatarId != null } if (recipient.isGroupRecipient && !isOpenGroupWithAvatar(recipient)) { val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf() diff --git a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 7fd5b3517c..7237cf9795 100644 --- a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -84,13 +84,17 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil ContactPhoto contactPhoto = recipient.getContactPhoto(); if (contactPhoto != null) { try { - setLargeIcon(GlideApp.with(context.getApplicationContext()) - .load(contactPhoto) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .circleCrop() - .submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), - context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height)) - .get()); + // AC: For some reason, if not use ".asBitmap()" method, the returned BitmapDrawable + // wraps a recycled bitmap and leads to a crash. + Bitmap iconBitmap = GlideApp.with(context.getApplicationContext()) + .asBitmap() + .load(contactPhoto) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .circleCrop() + .submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), + context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height)) + .get(); + setLargeIcon(iconBitmap); } catch (InterruptedException | ExecutionException e) { Log.w(TAG, e); setLargeIcon(getPlaceholderDrawable(context, recipient)); diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index c44240a30b..24c44b19ac 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -509,6 +509,11 @@ public class Recipient implements RecipientModifiedListener { if (notify) notifyListeners(); } + @Nullable + public synchronized Long getGroupAvatarId() { + return groupAvatarId; + } + public synchronized @Nullable Uri getMessageRingtone() { if (messageRingtone != null && messageRingtone.getScheme() != null && messageRingtone.getScheme().startsWith("file")) { return null; From fa02d4169144d21d64b0486efada178e2f3739cd Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Thu, 1 Oct 2020 21:26:28 +1000 Subject: [PATCH 05/14] Group database cleanup. --- .../thoughtcrime/securesms/database/GroupDatabase.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index 95db6f1b99..5c03fd4010 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -274,15 +274,6 @@ public class GroupDatabase extends Database implements LokiGroupDatabaseProtocol Recipient.applyCached(Address.fromSerialized(groupId), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId)); } - public boolean hasAvatar(String groupId) { - try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery( - "SELECT EXISTS(SELECT 1 FROM "+TABLE_NAME+" WHERE "+GROUP_ID+" == ? AND "+AVATAR+" NOT NULL LIMIT 1)", - new String[]{groupId})) { - cursor.moveToFirst(); - return cursor.getInt(0) == 1; - } - } - public void updateMembers(String groupId, List
members) { Collections.sort(members); From 501f3bbb871af44c5113f43c8044af9e868c2661 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 9 Oct 2020 13:57:52 +1100 Subject: [PATCH 06/14] Match iOS path maintenance changes --- .../securesms/loki/activities/PathActivity.kt | 2 +- .../loki/database/LokiAPIDatabase.kt | 59 +++++++++++-------- .../securesms/loki/views/PathStatusView.kt | 2 +- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt index fa96d6b2a1..9de15d852b 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt @@ -82,7 +82,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() { private fun update(isAnimated: Boolean) { pathRowsContainer.removeAllViews() - if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) { + if (OnionRequestAPI.paths.isNotEmpty()) { val path = OnionRequestAPI.paths.firstOrNull() ?: return finish() val dotAnimationRepeatInterval = path.count().toLong() * 1000 + 1000 val pathRows = path.mapIndexed { index, snode -> diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index 18fae93b87..e411117150 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -114,6 +114,29 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(snodePoolTable, row, "${Companion.dummyKey} = ?", wrap("dummy_key")) } + override fun setOnionRequestPaths(newValue: List>) { + // FIXME: This approach assumes either 1 or 2 paths of length 3 each. We should do better than this. + val database = databaseHelper.writableDatabase + fun set(indexPath: String, snode: Snode) { + var snodeAsString = "${snode.address}-${snode.port}" + val keySet = snode.publicKeySet + if (keySet != null) { + snodeAsString += "-${keySet.ed25519Key}-${keySet.x25519Key}" + } + val row = wrap(mapOf( Companion.indexPath to indexPath, Companion.snode to snodeAsString )) + database.insertOrUpdate(onionRequestPathTable, row, "${Companion.indexPath} = ?", wrap(indexPath)) + } + Log.d("Loki", "Persisting onion request paths to database.") + if (newValue.count() < 1) { return } + val path0 = newValue[0] + if (path0.count() != 3) { return } + set("0-0", path0[0]); set("0-1", path0[1]); set("0-2", path0[2]) + if (newValue.count() < 2) { return } + val path1 = newValue[1] + if (path1.count() != 3) { return } + set("1-0", path1[0]); set("1-1", path1[1]); set("1-2", path1[2]) + } + override fun getOnionRequestPaths(): List> { val database = databaseHelper.readableDatabase fun get(indexPath: String): Snode? { @@ -131,10 +154,16 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } } } - val path0Snode0 = get("0-0") ?: return listOf(); val path0Snode1 = get("0-1") ?: return listOf() - val path0Snode2 = get("0-2") ?: return listOf(); val path1Snode0 = get("1-0") ?: return listOf() - val path1Snode1 = get("1-1") ?: return listOf(); val path1Snode2 = get("1-2") ?: return listOf() - return listOf( listOf( path0Snode0, path0Snode1, path0Snode2 ), listOf( path1Snode0, path1Snode1, path1Snode2 ) ) + val result = mutableListOf>() + val path0Snode0 = get("0-0"); val path0Snode1 = get("0-1"); val path0Snode2 = get("0-2") + if (path0Snode0 != null && path0Snode1 != null && path0Snode2 != null) { + result.add(listOf( path0Snode0, path0Snode1, path0Snode2 )) + } + val path1Snode0 = get("1-0"); val path1Snode1 = get("1-1"); val path1Snode2 = get("1-2") + if (path1Snode0 != null && path1Snode1 != null && path1Snode2 != null) { + result.add(listOf( path1Snode0, path1Snode1, path1Snode2 )) + } + return result } override fun clearOnionRequestPaths() { @@ -147,28 +176,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( delete("1-1"); delete("1-2") } - override fun setOnionRequestPaths(newValue: List>) { - // TODO: Make this work with arbitrary paths - if (newValue.count() != 2) { return } - val path0 = newValue[0] - val path1 = newValue[1] - if (path0.count() != 3 || path1.count() != 3) { return } - Log.d("Loki", "Persisting onion request paths to database.") - val database = databaseHelper.writableDatabase - fun set(indexPath: String, snode: Snode) { - var snodeAsString = "${snode.address}-${snode.port}" - val keySet = snode.publicKeySet - if (keySet != null) { - snodeAsString += "-${keySet.ed25519Key}-${keySet.x25519Key}" - } - val row = wrap(mapOf( Companion.indexPath to indexPath, Companion.snode to snodeAsString )) - database.insertOrUpdate(onionRequestPathTable, row, "${Companion.indexPath} = ?", wrap(indexPath)) - } - set("0-0", path0[0]); set("0-1", path0[1]) - set("0-2", path0[2]); set("1-0", path1[0]) - set("1-1", path1[1]); set("1-2", path1[2]) - } - override fun getSwarm(publicKey: String): Set? { val database = databaseHelper.readableDatabase return database.get(swarmTable, "${Companion.swarmPublicKey} = ?", wrap(publicKey)) { cursor -> diff --git a/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt b/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt index e65630c144..77cfbe2505 100644 --- a/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt +++ b/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt @@ -85,7 +85,7 @@ class PathStatusView : View { private fun handlePathsBuiltEvent() { update() } private fun update() { - if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) { + if (OnionRequestAPI.paths.isNotEmpty()) { setBackgroundResource(R.drawable.accent_dot) mainColor = resources.getColorWithID(R.color.accent, context.theme) sessionShadowColor = resources.getColorWithID(R.color.accent, context.theme) From f9e8eb25497abb6cac58813187ea0c50bfbd0ba9 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 9 Oct 2020 14:38:29 +1100 Subject: [PATCH 07/14] Debug --- src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index e411117150..329b40fef8 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -127,6 +127,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(onionRequestPathTable, row, "${Companion.indexPath} = ?", wrap(indexPath)) } Log.d("Loki", "Persisting onion request paths to database.") + clearOnionRequestPaths() if (newValue.count() < 1) { return } val path0 = newValue[0] if (path0.count() != 3) { return } From d19a69567d4b6672e2e5a2dc290ce4ece526885f Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 19 Oct 2020 15:12:06 +1100 Subject: [PATCH 08/14] Clean --- .../securesms/database/GroupDatabase.java | 31 ++++++++++--------- .../database/helpers/SQLCipherOpenHelper.java | 4 +-- .../securesms/loki/activities/HomeActivity.kt | 2 +- .../securesms/loki/api/PublicChatManager.kt | 16 +++++----- .../securesms/loki/api/PublicChatPoller.kt | 3 +- .../loki/database/LokiAPIDatabase.kt | 25 +++++++-------- 6 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index 5c03fd4010..c5bd65e903 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -6,9 +6,10 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.text.TextUtils; import com.annimon.stream.Stream; @@ -21,7 +22,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 org.whispersystems.signalservice.loki.database.LokiOpenGroupDatabaseProtocol; import java.io.Closeable; import java.io.IOException; @@ -30,7 +31,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -public class GroupDatabase extends Database implements LokiGroupDatabaseProtocol { +public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProtocol { @SuppressWarnings("unused") private static final String TAG = GroupDatabase.class.getSimpleName(); @@ -242,36 +243,36 @@ public class GroupDatabase extends Database implements LokiGroupDatabaseProtocol } @Override - public void updateTitle(String groupId, String title) { + public void updateTitle(String groupID, String newValue) { ContentValues contentValues = new ContentValues(); - contentValues.put(TITLE, title); + contentValues.put(TITLE, newValue); databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", - new String[] {groupId}); + new String[] {groupID}); - Recipient recipient = Recipient.from(context, Address.fromSerialized(groupId), false); - recipient.setName(title); + Recipient recipient = Recipient.from(context, Address.fromSerialized(groupID), false); + recipient.setName(newValue); } - public void updateAvatar(String groupId, Bitmap avatar) { - updateAvatar(groupId, BitmapUtil.toByteArray(avatar)); + public void updateProfilePicture(String groupID, Bitmap newValue) { + updateProfilePicture(groupID, BitmapUtil.toByteArray(newValue)); } @Override - public void updateAvatar(String groupId, byte[] avatar) { + public void updateProfilePicture(String groupID, byte[] newValue) { long avatarId; - if (avatar != null) avatarId = Math.abs(new SecureRandom().nextLong()); + if (newValue != null) avatarId = Math.abs(new SecureRandom().nextLong()); else avatarId = 0; ContentValues contentValues = new ContentValues(2); - contentValues.put(AVATAR, avatar); + contentValues.put(AVATAR, newValue); contentValues.put(AVATAR_ID, avatarId); databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", - new String[] {groupId}); + new String[] {groupID}); - 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 void updateMembers(String groupId, List
members) { diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 400be1b952..d636857163 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -155,7 +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(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand()); db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand()); @@ -630,7 +630,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } if (oldVersion < lokiV15) { - db.execSQL(LokiAPIDatabase.getCreateOpenGroupAvatarCacheCommand()); + db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand()); db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand()); } diff --git a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 22159bcd3d..5db0535a33 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -347,7 +347,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe val apiDB = DatabaseFactory.getLokiAPIDatabase(this@HomeActivity) apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server) apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server) - apiDB.clearOpenGroupAvatarURL(publicChat.channel, publicChat.server) + apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server) ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server) } threadDB.deleteConversation(threadID) diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index 5cddbe2b6b..3a4a10915d 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -14,7 +14,7 @@ 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.PublicChatInfo import org.whispersystems.signalservice.loki.api.opengroups.PublicChat class PublicChatManager(private val context: Context) { @@ -68,18 +68,18 @@ class PublicChatManager(private val context: Context) { } } - public fun addChat(server: String, channel: Long, info: LokiPublicChatInfo): PublicChat { + public fun addChat(server: String, channel: Long, info: PublicChatInfo): PublicChat { val chat = PublicChat(channel, server, info.displayName, true) var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) - var avatar: Bitmap? = null + var profilePicture: Bitmap? = null // Create the group if we don't have one if (threadID < 0) { - if (!info.profilePictureURL.isEmpty()) { - val avatarBytes = ApplicationContext.getInstance(context).publicChatAPI - ?.downloadOpenGroupAvatar(server, info.profilePictureURL) - avatar = BitmapUtil.fromByteArray(avatarBytes) + if (info.profilePictureURL.isNotEmpty()) { + val profilePictureAsByteArray = ApplicationContext.getInstance(context).publicChatAPI + ?.downloadOpenGroupProfilePicture(server, info.profilePictureURL) + profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray) } - val result = GroupManager.createOpenGroup(chat.id, context, avatar, chat.displayName) + val result = GroupManager.createOpenGroup(chat.id, context, profilePicture, chat.displayName) threadID = result.threadId } DatabaseFactory.getLokiThreadDatabase(context).setPublicChat(chat, threadID) diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt index 374f8b68c6..ca73a77b3e 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt @@ -46,7 +46,8 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context) val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context) - PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase) + val openGroupDatabase = DatabaseFactory.getGroupDatabase(context) + PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase, openGroupDatabase) }() // endregion diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index 0d81d464a5..52bb965cfa 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -6,7 +6,6 @@ import android.util.Log import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.utilities.* -import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.loki.api.Snode import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink @@ -71,10 +70,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);" + // Open group profile picture cache + private val openGroupProfilePictureTable = "open_group_avatar_cache" + private val openGroupProfilePicture = "open_group_avatar" + @JvmStatic val createOpenGroupProfilePictureTableCommand = "CREATE TABLE $openGroupProfilePictureTable ($publicChatID STRING PRIMARY KEY, $openGroupProfilePicture TEXT NULLABLE DEFAULT NULL);" // region Deprecated private val deviceLinkCache = "loki_pairing_authorisation_cache" @@ -347,25 +346,25 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(openGroupPublicKeyTable, row, "${LokiAPIDatabase.server} = ?", wrap(server)) } - override fun getOpenGroupAvatarURL(group: Long, server: String): String? { + override fun getOpenGroupProfilePictureURL(group: Long, server: String): String? { val database = databaseHelper.readableDatabase val index = "$server.$group" - return database.get(openGroupAvatarCacheTable, "$publicChatID = ?", wrap(index)) { cursor -> - cursor.getString(openGroupAvatar) + return database.get(openGroupProfilePictureTable, "$publicChatID = ?", wrap(index)) { cursor -> + cursor.getString(openGroupProfilePicture) }?.toString() } - override fun setOpenGroupAvatarURL(group: Long, server: String, url: String) { + override fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: 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)) + val row = wrap(mapOf(publicChatID to index, openGroupProfilePicture to newValue)) + database.insertOrUpdate(openGroupProfilePictureTable, row, "$publicChatID = ?", wrap(index)) } - fun clearOpenGroupAvatarURL(group: Long, server: String): Boolean { + fun clearOpenGroupProfilePictureURL(group: Long, server: String): Boolean { val database = databaseHelper.writableDatabase val index = "$server.$group" - return database.delete(openGroupAvatarCacheTable, "$publicChatID == ?", arrayOf(index)) > 0 + return database.delete(openGroupProfilePictureTable, "$publicChatID = ?", arrayOf(index)) > 0 } // region Deprecated From bdbb03687ef6e06a36c7747a5377866a320a09d8 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 19 Oct 2020 15:21:15 +1100 Subject: [PATCH 09/14] Fix build --- src/org/thoughtcrime/securesms/ApplicationContext.java | 4 +++- .../securesms/conversation/ConversationActivity.java | 3 +-- .../securesms/dependencies/SignalCommunicationModule.java | 1 + src/org/thoughtcrime/securesms/groups/GroupManager.java | 6 +++--- src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 29a668776d..44531d4c4b 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule; @@ -305,7 +306,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); - publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB); + GroupDatabase groupDB = DatabaseFactory.getGroupDatabase(this); + publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB, groupDB); return publicChatAPI; } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index e90be23712..b89f3eed65 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -464,12 +464,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity publicChatAPI.getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(info -> { String groupId = GroupUtil.getEncodedOpenGroupId(publicChat.getId().getBytes()); - publicChatAPI.updateOpenGroupProfileIfNeeded( + publicChatAPI.updateProfileIfNeeded( publicChat.getChannel(), publicChat.getServer(), groupId, info, - DatabaseFactory.getGroupDatabase(this), false); runOnUiThread(ConversationActivity.this::updateSubtitleTextView); diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index 520761b536..a7449ca58d 100644 --- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -159,6 +159,7 @@ public class SignalCommunicationModule { DatabaseFactory.getLokiPreKeyBundleDatabase(context), new SessionResetImplementation(context), DatabaseFactory.getLokiUserDatabase(context), + DatabaseFactory.getGroupDatabase(context), ((ApplicationContext)context.getApplicationContext()).broadcaster); } else { this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe()); diff --git a/src/org/thoughtcrime/securesms/groups/GroupManager.java b/src/org/thoughtcrime/securesms/groups/GroupManager.java index 81f6feec33..694627f629 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/GroupManager.java @@ -85,7 +85,7 @@ public class GroupManager { groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>(adminAddresses)); if (!mms) { - groupDatabase.updateAvatar(groupId, avatarBytes); + groupDatabase.updateProfilePicture(groupId, avatarBytes); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true); return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses); } else { @@ -125,7 +125,7 @@ public class GroupManager { memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>()); - groupDatabase.updateAvatar(groupId, avatarBytes); + groupDatabase.updateProfilePicture(groupId, avatarBytes); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); return new GroupActionResult(groupRecipient, threadID); @@ -148,7 +148,7 @@ public class GroupManager { groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses)); groupDatabase.updateAdmins(groupId, new LinkedList<>(adminAddresses)); groupDatabase.updateTitle(groupId, name); - groupDatabase.updateAvatar(groupId, avatarBytes); + groupDatabase.updateProfilePicture(groupId, avatarBytes); if (!GroupUtil.isMmsGroup(groupId)) { return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses); diff --git a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index 0b2db43157..f2b95a46f0 100644 --- a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -95,7 +95,7 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType { InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE); Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500); - database.updateAvatar(groupId, avatar); + database.updateProfilePicture(groupId, avatar); inputStream.close(); } } catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) { From 00e45174e471f809625f3d1e08fa7a94263c0d0e Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 27 Oct 2020 14:41:03 +1100 Subject: [PATCH 10/14] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bf872b1f1d..231d0b4760 100644 --- a/build.gradle +++ b/build.gradle @@ -181,7 +181,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 111 +def canonicalVersionCode = 112 def canonicalVersionName = "1.6.2" def postFixSize = 10 From 8070b9dd4ea27808de23413cc852f0059046dbb2 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 28 Oct 2020 10:14:53 +1100 Subject: [PATCH 11/14] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 231d0b4760..70b8b428fe 100644 --- a/build.gradle +++ b/build.gradle @@ -181,7 +181,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 112 +def canonicalVersionCode = 113 def canonicalVersionName = "1.6.2" def postFixSize = 10 From 49b588a6e3d3b09e999fb5107c3b01b0b2fba38c Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 28 Oct 2020 11:31:53 +1100 Subject: [PATCH 12/14] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 70b8b428fe..ae728ee7f0 100644 --- a/build.gradle +++ b/build.gradle @@ -181,7 +181,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 113 +def canonicalVersionCode = 114 def canonicalVersionName = "1.6.2" def postFixSize = 10 From 6ad688b8d024ba3076fad59bc0b987b86b7aa59c Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 28 Oct 2020 13:57:59 +1100 Subject: [PATCH 13/14] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ae728ee7f0..57c098a103 100644 --- a/build.gradle +++ b/build.gradle @@ -181,7 +181,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 114 +def canonicalVersionCode = 115 def canonicalVersionName = "1.6.2" def postFixSize = 10 From c8f7d788b9a896425c66e91d24f226e0de0954aa Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 28 Oct 2020 15:04:47 +1100 Subject: [PATCH 14/14] Fix migration issue --- .../securesms/conversation/ConversationActivity.java | 10 +++++----- .../database/helpers/SQLCipherOpenHelper.java | 8 ++++++-- .../securesms/loki/views/ProfilePictureView.kt | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index b89f3eed65..12c8e337ce 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -465,11 +465,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity String groupId = GroupUtil.getEncodedOpenGroupId(publicChat.getId().getBytes()); publicChatAPI.updateProfileIfNeeded( - publicChat.getChannel(), - publicChat.getServer(), - groupId, - info, - false); + publicChat.getChannel(), + publicChat.getServer(), + groupId, + info, + false); runOnUiThread(ConversationActivity.this::updateSubtitleTextView); return Unit.INSTANCE; diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index d636857163..cae4f03c09 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -92,8 +92,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV13 = 34; private static final int lokiV14_BACKUP_FILES = 35; private static final int lokiV15 = 36; + private static final int lokiV16 = 37; - private static final int DATABASE_VERSION = lokiV15; + private static final int DATABASE_VERSION = lokiV16; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -630,10 +631,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } if (oldVersion < lokiV15) { - db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand()); db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand()); } + if (oldVersion < lokiV16) { + db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index f8b0d5b6a3..85105609c8 100644 --- a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -68,10 +68,10 @@ class ProfilePictureView : RelativeLayout { return result ?: publicKey } } - fun isOpenGroupWithAvatar(recipient: Recipient): Boolean { + fun isOpenGroupWithProfilePicture(recipient: Recipient): Boolean { return recipient.isOpenGroupRecipient && recipient.groupAvatarId != null } - if (recipient.isGroupRecipient && !isOpenGroupWithAvatar(recipient)) { + if (recipient.isGroupRecipient && !isOpenGroupWithProfilePicture(recipient)) { val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf() users.remove(TextSecurePreferences.getLocalNumber(context)) val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)