From 31ad7a40de0d83f64515dca57bb73d88afda84c5 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 31 Oct 2019 11:36:52 +1100 Subject: [PATCH] Stop main thread from being blocked. Refactoring. --- .../components/TypingStatusSender.java | 17 +-- .../conversation/ConversationActivity.java | 75 ++++++----- .../securesms/loki/MultiDeviceUtilities.kt | 30 +++-- .../securesms/loki/PushMessageSyncSendJob.kt | 1 + .../securesms/loki/SeedActivity.kt | 1 - .../notifications/MarkReadReceiver.java | 21 +-- .../securesms/sms/MessageSender.java | 127 +++++++----------- .../securesms/util/TextSecurePreferences.java | 2 +- 8 files changed, 138 insertions(+), 136 deletions(-) diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java index b813f5c63e..dd28616d8e 100644 --- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -91,15 +91,16 @@ public class TypingStatusSender { ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted)); return; } - - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()); - for (String device : devices) { - Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); - long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); - if (deviceThreadID > -1) { - ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted)); + LokiStorageAPI.shared.getAllDevicePublicKeysAsync(recipient.getAddress().serialize()).success(devices -> { + for (String device : devices) { + Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); + long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); + if (deviceThreadID > -1) { + ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted)); + } } - } + return Unit.INSTANCE; + }); } private class StartRunnable implements Runnable { diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index e3f6769fe3..2c80d636f7 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -249,7 +249,11 @@ import java.util.concurrent.atomic.AtomicInteger; import kotlin.Unit; import network.loki.messenger.R; +import nl.komponents.kovenant.Kovenant; +import nl.komponents.kovenant.KovenantApi; +import nl.komponents.kovenant.Promise; +import static nl.komponents.kovenant.KovenantApi.task; import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; @@ -2185,44 +2189,50 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void handleThreadFriendRequestStatusChanged(long threadID) { - Util.runOnMain(() -> { - boolean shouldUpdateInputPanel = true; if (threadID != this.threadId) { Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { - // We should update our input if this thread is a part of the other threads device - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(threadRecipient.getAddress().serialize()); - shouldUpdateInputPanel = devices.contains(recipient.getAddress().serialize()); - } else { - shouldUpdateInputPanel = false; + LokiStorageAPI.shared.getAllDevicePublicKeysAsync(threadRecipient.getAddress().serialize()).success(devices -> { + // We should update our input if this thread is a part of the other threads device + if (devices.contains(recipient.getAddress().serialize())) { + this.updateInputPanel(); + } + return Unit.INSTANCE; + }); } + return; } - if (shouldUpdateInputPanel) { - this.updateInputPanel(); - } - }); + + this.updateInputPanel(); } private void updateInputPanel() { - if (recipient.isGroupRecipient()) { + if (recipient.isGroupRecipient() || isNoteToSelf()) { + setInputPanelEnabled(true); + return; + } + + task(() -> { + // Run the functions below in a background thread since they are blocking + return MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient) || !hasPendingFriendRequestWithAnyLinkedDevice(); + }).success(shouldEnableInput -> { + setInputPanelEnabled(shouldEnableInput); + return Unit.INSTANCE; + }); + } + + private void setInputPanelEnabled(boolean enabled) { + Util.runOnMain(() -> { updateToggleButtonState(); - inputPanel.setEnabled(true); - inputPanel.composeText.requestFocus(); - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(inputPanel.composeText, 0); - } - boolean hasPendingFriendRequest = !recipient.isGroupRecipient() && hasPendingFriendRequestWithAnyLinkedDevice(); - boolean isFriendsWithAnyLinkedDevices = MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient); - boolean shouldEnableInput = isFriendsWithAnyLinkedDevices || !hasPendingFriendRequest; - updateToggleButtonState(); - inputPanel.setEnabled(shouldEnableInput); - int hintID = shouldEnableInput ? R.string.activity_conversation_default_hint : R.string.activity_conversation_pending_friend_request_hint; - inputPanel.setHint(getResources().getString(hintID)); - if (shouldEnableInput) { - inputPanel.composeText.requestFocus(); - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(inputPanel.composeText, 0); - } + int hintID = enabled ? R.string.activity_conversation_default_hint : R.string.activity_conversation_pending_friend_request_hint; + inputPanel.setHint(getResources().getString(hintID)); + inputPanel.setEnabled(enabled); + if (enabled) { + inputPanel.composeText.requestFocus(); + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(inputPanel.composeText, 0); + } + }); } private void sendMessage() { @@ -2428,7 +2438,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void updateToggleButtonState() { // Don't allow attachments if we're not friends LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId); - if (!recipient.isGroupRecipient() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { + if (!isNoteToSelf() && !recipient.isGroupRecipient() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { buttonToggle.display(sendButton); quickAttachmentToggle.hide(); inlineAttachmentToggle.hide(); @@ -3047,6 +3057,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public boolean hasPendingFriendRequestWithAnyLinkedDevice() { if (recipient.isGroupRecipient()) return false; + // This call will block the thread that is being run on! be careful Map map = MultiDeviceUtilities.getAllDeviceFriendRequestStatuses(this, recipient.getAddress().serialize()); for (LokiThreadFriendRequestStatus status : map.values()) { if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { @@ -3056,5 +3067,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return false; } + + public boolean isNoteToSelf() { + return TextSecurePreferences.getLocalNumber(this).equalsIgnoreCase(recipient.getAddress().serialize()); + } // endregion } diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index d3c8b17cad..ccee4f7c2c 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -4,6 +4,8 @@ package org.thoughtcrime.securesms.loki import android.content.Context import android.os.Handler import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.map +import nl.komponents.kovenant.toFailVoid import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address @@ -20,6 +22,10 @@ import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.utilities.retryIfNeeded +/* + All functions within this class, excluding the ones which return promises, BLOCK the thread! Don't run them on the main thread! + */ + fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Map { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val keys = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey) @@ -33,18 +39,20 @@ fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: Str return map } -fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Map { +fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise, Unit> { val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) - val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).toMutableSet() - if (hexEncodedPublicKey != userHexEncodedPublicKey) { - devices.remove(userHexEncodedPublicKey) - } - val friends = getFriendPublicKeys(context, devices) - val map = mutableMapOf() - for (device in devices) { - map[device] = friends.contains(device) - } - return map + return LokiStorageAPI.shared.getAllDevicePublicKeysAsync(hexEncodedPublicKey).map { keys -> + val devices = keys.toMutableSet() + if (hexEncodedPublicKey != userHexEncodedPublicKey) { + devices.remove(userHexEncodedPublicKey) + } + val friends = getFriendPublicKeys(context, devices) + val friendMap = mutableMapOf() + for (device in devices) { + friendMap[device] = friends.contains(device) + } + friendMap + }.toFailVoid() } fun getFriendCount(context: Context, devices: Set): Int { diff --git a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt index 9fb7f9d75e..f5848cbb07 100644 --- a/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushMessageSyncSendJob.kt @@ -39,6 +39,7 @@ class PushMessageSyncSendJob private constructor( constructor(messageID: Long, recipient: Address, timestamp: Long, message: ByteArray, ttl: Int) : this(Parameters.Builder() .addConstraint(NetworkConstraint.KEY) + .setQueue(KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(3) .build(), diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 8a537e1de6..1d494a8711 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.util.Hex import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.curve25519.Curve25519 import org.whispersystems.libsignal.util.KeyHelper -import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.utilities.Analytics diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index ff5a1477ee..f92ff642f0 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.service.ExpiringMessageManager; +import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import java.util.LinkedList; @@ -29,6 +30,7 @@ import java.util.List; import java.util.Map; import kotlin.Unit; +import kotlin.contracts.Returns; public class MarkReadReceiver extends BroadcastReceiver { @@ -91,16 +93,17 @@ public class MarkReadReceiver extends BroadcastReceiver { for (Address address : addressMap.keySet()) { List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); - - Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, address.serialize()); - for (Map.Entry entry : devices.entrySet()) { - String device = entry.getKey(); - boolean isFriend = entry.getValue(); - // Loki - This also prevents read receipts from being sent in group chats as they don't maintain a friend request status - if (isFriend) { - ApplicationContext.getInstance(context).getJobManager().add(new SendReadReceiptJob(Address.fromSerialized(device), timestamps)); + MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, address.serialize()).success(devices -> { + for (Map.Entry entry : devices.entrySet()) { + String device = entry.getKey(); + boolean isFriend = entry.getValue(); + // Loki - This also prevents read receipts from being sent in group chats as they don't maintain a friend request status + if (isFriend) { + Util.runOnMain(() -> ApplicationContext.getInstance(context).getJobManager().add(new SendReadReceiptJob(Address.fromSerialized(device), timestamps))); + } } - } + return Unit.INSTANCE; + }); } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 91e8bfc8ed..e15cda3de8 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -72,6 +72,8 @@ public class MessageSender { private static final String TAG = MessageSender.class.getSimpleName(); + private enum MessageType { TEXT, MEDIA } + public static void sendBackgroundMessageToAllDevices(Context context, String contactHexEncodedPublicKey) { // Send the background message to the original pubkey sendBackgroundMessage(context, contactHexEncodedPublicKey); @@ -254,91 +256,64 @@ public class MessageSender { } private static void sendTextPush(Context context, Recipient recipient, long messageId) { - JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - - // Just send the message normally if it's a group message - String recipientPublicKey = recipient.getAddress().serialize(); - if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) { - jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); - return; - } - - // Note to self - boolean isNoteToSelf = MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()); - if (isNoteToSelf) { - jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); - return; - } - - boolean hasSentSyncMessage = false; - - Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey); - int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); - for (Map.Entry entry : devices.entrySet()) { - String devicePublicKey = entry.getKey(); - boolean isFriend = entry.getValue(); - - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; - - if (isFriend) { - // Send a normal message if the user is friends with the recipient - // We should also send a sync message if we haven't already sent one - boolean shouldSendSyncMessage = !hasSentSyncMessage && MultiDeviceUtilities.shouldSendSycMessage(context, address); - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); - hasSentSyncMessage = shouldSendSyncMessage; - } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = (friendCount > 0); - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); - } - } + sendMessagePush(context, MessageType.TEXT, recipient, messageId); } private static void sendMediaPush(Context context, Recipient recipient, long messageId) { + sendMessagePush(context, MessageType.MEDIA, recipient, messageId); + } + + private static void sendMessagePush(Context context, MessageType type, Recipient recipient, long messageId) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - // Just send the message normally if it's a group message + // Just send the message normally if it's a group message or we're sending to one of our devices + // TODO: Test how badly this will block the thread String recipientPublicKey = recipient.getAddress().serialize(); - if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey)) { - PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); - return; - } - - // Note to self - boolean isNoteToSelf = MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()); - if (isNoteToSelf) { - jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); - return; - } - - boolean hasSentSyncMessage = false; - - Map devices = MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey); - int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); - for (Map.Entry entry : devices.entrySet()) { - String devicePublicKey = entry.getKey(); - boolean isFriend = entry.getValue(); - - Address address = Address.fromSerialized(devicePublicKey); - long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; - - if (isFriend) { - // Send a normal message if the user is friends with the recipient - // We should also send a sync message if we haven't already sent one - boolean shouldSendSyncMessage = !hasSentSyncMessage && MultiDeviceUtilities.shouldSendSycMessage(context, address); - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); - hasSentSyncMessage = shouldSendSyncMessage; + if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress())) { + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); } else { - // Send friend requests to non friends. If the user is friends with any - // of the devices then send out a default friend request message. - boolean isFriendsWithAny = (friendCount > 0); - String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); + jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); } + return; } + + boolean[] hasSentSyncMessage = { false }; + MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey).success(devices -> { + int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); + Util.runOnMain(() -> { + for (Map.Entry entry : devices.entrySet()) { + String devicePublicKey = entry.getKey(); + boolean isFriend = entry.getValue(); + + Address address = Address.fromSerialized(devicePublicKey); + long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; + + if (isFriend) { + // Send a normal message if the user is friends with the recipient + // We should also send a sync message if we haven't already sent one + boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilities.shouldSendSycMessage(context, address); + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + } else { + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + } + hasSentSyncMessage[0] = shouldSendSyncMessage; + } else { + // Send friend requests to non friends. If the user is friends with any + // of the devices then send out a default friend request message. + boolean isFriendsWithAny = (friendCount > 0); + String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null; + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false); + } else { + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false)); + } + } + } + }); + return Unit.INSTANCE; + }); } private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) { diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 46499f05f0..bd72a58b59 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -640,7 +640,7 @@ public class TextSecurePreferences { } public static void setLocalNumber(Context context, String localNumber) { - setStringPreference(context, LOCAL_NUMBER_PREF, localNumber); + setStringPreference(context, LOCAL_NUMBER_PREF, localNumber.toLowerCase()); } public static void removeLocalNumber(Context context) {