diff --git a/src/org/thoughtcrime/securesms/database/Address.java b/src/org/thoughtcrime/securesms/database/Address.java index 0969e0e14d..d777b6e86b 100644 --- a/src/org/thoughtcrime/securesms/database/Address.java +++ b/src/org/thoughtcrime/securesms/database/Address.java @@ -105,7 +105,7 @@ public class Address implements Parcelable, Comparable
{ public boolean isGroup() { return GroupUtil.isEncodedGroup(address); } - public boolean isSignalGroup() { return !isPublicChat() && !isRSSFeed(); } + public boolean isSignalGroup() { return GroupUtil.isSignalGroup(address); } public boolean isPublicChat() { return GroupUtil.isPublicChat(address); } diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index 9e1bf481f1..1e7a3dfb4a 100644 --- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation; +import org.thoughtcrime.securesms.loki.MultiDeviceOpenGroupUpdateJob; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.push.MessageSenderEventListener; @@ -111,7 +112,8 @@ import network.loki.messenger.BuildConfig; MultiDeviceStickerPackOperationJob.class, MultiDeviceStickerPackSyncJob.class, LinkPreviewRepository.class, - PushMessageSyncSendJob.class}) + PushMessageSyncSendJob.class, + MultiDeviceOpenGroupUpdateJob.class}) public class SignalCommunicationModule { diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 266d6194df..3a375a6169 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; +import org.thoughtcrime.securesms.loki.MultiDeviceOpenGroupUpdateJob; import org.thoughtcrime.securesms.loki.PushBackgroundMessageSendJob; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; @@ -73,6 +74,7 @@ public final class JobManagerFactories { put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); put(PushMessageSyncSendJob.KEY, new PushMessageSyncSendJob.Factory()); put(PushBackgroundMessageSendJob.KEY, new PushBackgroundMessageSendJob.Factory()); + put(MultiDeviceOpenGroupUpdateJob.KEY, new MultiDeviceOpenGroupUpdateJob.Factory()); }}; } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index d85fe7b720..4fc2508de9 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.StickerRecord; import org.thoughtcrime.securesms.dependencies.InjectableType; +import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; @@ -72,6 +73,7 @@ import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation; import org.thoughtcrime.securesms.loki.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; +import org.thoughtcrime.securesms.loki.redesign.utilities.OpenGroupUtilities; import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity; import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities; import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase; @@ -138,6 +140,7 @@ import org.whispersystems.signalservice.loki.api.DeviceLink; import org.whispersystems.signalservice.loki.api.DeviceLinkingSession; import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; +import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.LokiFileServerAPI; import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; @@ -394,6 +397,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get()); else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get()); else if (syncMessage.getGroups().isPresent()) handleGroupSyncMessage(content, syncMessage.getGroups().get()); + else if (syncMessage.getOpenGroups().isPresent()) handleOpenGroupSyncMessage(syncMessage.getOpenGroups().get()); else Log.w(TAG, "Contains no known sync types..."); } else if (content.getCallMessage().isPresent()) { Log.i(TAG, "Got call message..."); @@ -751,6 +755,24 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } + private void handleOpenGroupSyncMessage(@NonNull List openGroups) { + try { + for (LokiPublicChat openGroup : openGroups) { + long threadID = GroupManager.getPublicChatThreadId(openGroup.getId(), context); + if (threadID > -1) continue; + + String url = openGroup.getServer(); + long channel = openGroup.getChannel(); + OpenGroupUtilities.addGroup(context, url, channel).fail(e -> { + Log.d("Loki", "Failed to sync open group: " + url + " due to error: " + e + "."); + return Unit.INSTANCE; + }); + } + } catch (Exception e) { + Log.d("Loki", "Failed to sync open groups due to error: " + e + "."); + } + } + private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, @NonNull SentTranscriptMessage message) throws StorageFailedException diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceOpenGroupUpdateJob.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceOpenGroupUpdateJob.kt new file mode 100644 index 0000000000..0ef7ca67ed --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceOpenGroupUpdateJob.kt @@ -0,0 +1,80 @@ +package org.thoughtcrime.securesms.loki + +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.dependencies.InjectableType +import org.thoughtcrime.securesms.groups.GroupManager +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.jobs.BaseJob +import org.thoughtcrime.securesms.logging.Log +import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.whispersystems.signalservice.api.SignalServiceMessageSender +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage +import org.whispersystems.signalservice.loki.api.LokiPublicChat +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters), InjectableType { + + companion object { + const val KEY = "MultiDeviceGroupUpdateJob" + } + + @Inject + lateinit var messageSender: SignalServiceMessageSender + + constructor() : this(Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setQueue("MultiDeviceGroupUpdateJob") + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(Parameters.UNLIMITED) + .build()) + + override fun getFactoryKey(): String { return KEY } + + override fun serialize(): Data { return Data.EMPTY } + + @Throws(Exception::class) + public override fun onRun() { + if (!TextSecurePreferences.isMultiDevice(context)) { + Log.d("Loki", "Not multi device; aborting...") + return + } + + val openGroups = mutableListOf() + DatabaseFactory.getGroupDatabase(context).groups.use { reader -> + while (true) { + val record = reader.next ?: return@use + if (!record.isPublicChat) { continue; } + + val threadID = GroupManager.getThreadIdFromGroupId(record.encodedId, context) + val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) + if (openGroup != null) { + openGroups.add(openGroup) + } + } + } + + if (openGroups.size > 0) { + messageSender.sendMessage(0, SignalServiceSyncMessage.forOpenGroups(openGroups), + UnidentifiedAccessUtil.getAccessForSync(context)) + } else { + Log.d("Loki", "No open groups to sync.") + } + } + + public override fun onShouldRetry(exception: Exception): Boolean { + return false + } + + override fun onCanceled() { } + + class Factory : Job.Factory { + + override fun create(parameters: Parameters, data: Data): MultiDeviceOpenGroupUpdateJob { + return MultiDeviceOpenGroupUpdateJob(parameters) + } + } +} diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt index 3993a93f10..44812f438d 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt @@ -194,7 +194,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe private fun openConversation(thread: ThreadRecord) { val intent = Intent(this, ConversationActivity::class.java) - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, thread.recipient.getAddress()) + intent.putExtra(ConversationActivity.ADDRESS_EXTRA, thread.recipient.address) intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, thread.threadId) intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, thread.distributionType) intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis()) @@ -255,9 +255,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe val dialog = AlertDialog.Builder(activity) dialog.setMessage(dialogMessage) dialog.setPositiveButton(R.string.yes) { _, _ -> - val isGroup = recipient.isGroupRecipient + val isClosedGroup = recipient.address.isSignalGroup // Send a leave group message if this is an active closed group - if (isGroup && DatabaseFactory.getGroupDatabase(activity).isActive(recipient.address.toGroupString())) { + if (isClosedGroup && DatabaseFactory.getGroupDatabase(activity).isActive(recipient.address.toGroupString())) { if (!GroupUtil.leaveGroup(activity, recipient)) { Toast.makeText(activity, "Couldn't leave group", Toast.LENGTH_LONG).show() clearView(activity.recyclerView, viewHolder) @@ -267,10 +267,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe // Archive the conversation and then delete it after 10 seconds (the case where the // app was closed before the conversation could be deleted is handled in onCreate) threadDatabase.archiveConversation(threadID) + val delay = if (isClosedGroup) 10000L else 1000L val handler = Handler() - handler.postDelayed(deleteThread, 10000) + handler.postDelayed(deleteThread, delay) // Notify the user - val toastMessage = if (isGroup) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message + val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message Toast.makeText(activity, toastMessage, Toast.LENGTH_LONG).show() } dialog.setNegativeButton(R.string.no) { _, _ -> diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt index 6d3b940378..9364419d24 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt @@ -16,14 +16,12 @@ import kotlinx.android.synthetic.main.fragment_enter_chat_url.* import network.loki.messenger.R import nl.komponents.kovenant.ui.failUi import nl.komponents.kovenant.ui.successUi -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.crypto.ProfileKeyUtil -import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.loki.redesign.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragmentDelegate -import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.thoughtcrime.securesms.sms.MessageSender class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { private val adapter = JoinPublicChatActivityAdapter(this) @@ -68,19 +66,11 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode return Toast.makeText(this, "Invalid URL", Toast.LENGTH_SHORT).show() } showLoader() - val application = ApplicationContext.getInstance(this) + val channel: Long = 1 - val displayName = TextSecurePreferences.getProfileName(this) - val lokiPublicChatAPI = application.lokiPublicChatAPI!! - application.lokiPublicChatManager.addChat(url, channel).successUi { - DatabaseFactory.getLokiAPIDatabase(this).removeLastMessageServerID(channel, url) - DatabaseFactory.getLokiAPIDatabase(this).removeLastDeletionServerID(channel, url) - lokiPublicChatAPI.getMessages(channel, url) - lokiPublicChatAPI.setDisplayName(displayName, url) - lokiPublicChatAPI.join(channel, url) - val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(this) - val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(this) - lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl) + OpenGroupUtilities.addGroup(this, url, channel).success { + MessageSender.syncAllOpenGroups(this) + }.successUi { finish() }.failUi { hideLoader() diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt index 1def8899cd..d9147fbfdd 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt @@ -154,6 +154,7 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager Timer().schedule(4000) { MessageSender.syncAllGroups(this@LinkedDevicesActivity) MessageSender.syncAllContacts(this@LinkedDevicesActivity, Address.fromSerialized(deviceLink.slaveHexEncodedPublicKey)) + MessageSender.syncAllOpenGroups(this@LinkedDevicesActivity) } }.fail { LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem diff --git a/src/org/thoughtcrime/securesms/loki/redesign/utilities/OpenGroupUtilities.kt b/src/org/thoughtcrime/securesms/loki/redesign/utilities/OpenGroupUtilities.kt new file mode 100644 index 0000000000..87f7afdf6b --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/redesign/utilities/OpenGroupUtilities.kt @@ -0,0 +1,37 @@ +package org.thoughtcrime.securesms.loki.redesign.utilities + +import android.content.Context +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.then +import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.groups.GroupManager +import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.whispersystems.signalservice.loki.api.LokiPublicChat + +object OpenGroupUtilities { + + @JvmStatic fun addGroup(context: Context, url: String, channel: Long): Promise { + // Check for an existing group + val groupID = LokiPublicChat.getId(channel, url) + val threadID = GroupManager.getPublicChatThreadId(groupID, context) + val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) + if (openGroup != null) { return Promise.of(openGroup) } + // Add the new group + val application = ApplicationContext.getInstance(context) + val displayName = TextSecurePreferences.getProfileName(context) + val lokiPublicChatAPI = application.lokiPublicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.") + return application.lokiPublicChatManager.addChat(url, channel).then { group -> + DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url) + DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url) + lokiPublicChatAPI.getMessages(channel, url) + lokiPublicChatAPI.setDisplayName(displayName, url) + lokiPublicChatAPI.join(channel, url) + val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context) + val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(context) + lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl) + group + } + } +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 80a4baa482..7a72ac68d2 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.BackgroundMessage; import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt; +import org.thoughtcrime.securesms.loki.MultiDeviceOpenGroupUpdateJob; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.PushBackgroundMessageSendJob; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; @@ -86,6 +87,10 @@ public class MessageSender { ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceGroupUpdateJob()); } + public static void syncAllOpenGroups(Context context) { + ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceOpenGroupUpdateJob()); + } + /** * Send a contact sync message to all our devices telling them that we want to sync `contact` */ diff --git a/src/org/thoughtcrime/securesms/util/GroupUtil.java b/src/org/thoughtcrime/securesms/util/GroupUtil.java index d4bdffa291..907a21217e 100644 --- a/src/org/thoughtcrime/securesms/util/GroupUtil.java +++ b/src/org/thoughtcrime/securesms/util/GroupUtil.java @@ -4,15 +4,13 @@ import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import android.widget.Toast; import com.google.protobuf.ByteString; -import network.loki.messenger.R; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.database.GroupDatabase.*; +import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; @@ -26,6 +24,8 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import network.loki.messenger.R; + import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; public class GroupUtil { @@ -87,6 +87,10 @@ public class GroupUtil { return groupId.startsWith(ENCODED_RSS_FEED_GROUP_PREFIX); } + public static boolean isSignalGroup(@NonNull String groupId) { + return groupId.startsWith(ENCODED_SIGNAL_GROUP_PREFIX); + } + @WorkerThread public static Optional createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) { String encodedGroupId = groupRecipient.getAddress().toGroupString(); @@ -114,6 +118,8 @@ public class GroupUtil { } public static boolean leaveGroup(@NonNull Context context, Recipient groupRecipient) { + if (!groupRecipient.getAddress().isSignalGroup()) { return true; } + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); Optional leaveMessage = GroupUtil.createGroupLeaveMessage(context, groupRecipient);