mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-12 11:14:20 +00:00
Fix various session reset issues
This commit is contained in:
parent
9b329cbd34
commit
a7b94d188f
@ -490,7 +490,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||||
lokiPoller = new LokiPoller(userPublicKey, apiDB, protos -> {
|
lokiPoller = new LokiPoller(userPublicKey, apiDB, protos -> {
|
||||||
for (SignalServiceProtos.Envelope proto : protos) {
|
for (SignalServiceProtos.Envelope proto : protos) {
|
||||||
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto));
|
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
|
||||||
}
|
}
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
|
@ -207,7 +207,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
SignalServiceEnvelope envelope = database.get(messageId);
|
SignalServiceEnvelope envelope = database.get(messageId);
|
||||||
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent();
|
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent();
|
||||||
|
|
||||||
handleMessage(envelope, optionalSmsMessageId);
|
handleMessage(envelope, optionalSmsMessageId, false);
|
||||||
database.delete(messageId);
|
database.delete(messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +222,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processMessage(@NonNull SignalServiceEnvelope envelope) {
|
public void processMessage(@NonNull SignalServiceEnvelope envelope, boolean isPushNotification) {
|
||||||
synchronized (PushReceivedJob.RECEIVE_LOCK) {
|
synchronized (PushReceivedJob.RECEIVE_LOCK) {
|
||||||
if (needsMigration()) {
|
if (needsMigration()) {
|
||||||
Log.w(TAG, "Skipping and storing envelope, waiting for migration...");
|
Log.w(TAG, "Skipping and storing envelope, waiting for migration...");
|
||||||
@ -231,7 +231,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage(envelope, Optional.absent());
|
handleMessage(envelope, Optional.absent(), isPushNotification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional<Long> smsMessageId) {
|
private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional<Long> smsMessageId, boolean isPushNotification) {
|
||||||
try {
|
try {
|
||||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||||
@ -378,7 +378,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||||
} catch (ProtocolInvalidMessageException e) {
|
} catch (ProtocolInvalidMessageException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
|
if (!isPushNotification) { // This can be triggered if a PN encrypted with an old session comes in after the user performed a session reset
|
||||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||||
|
}
|
||||||
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
|
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||||
|
@ -57,7 +57,7 @@ public class PushNotificationReceiveJob extends PushReceivedJob implements Injec
|
|||||||
synchronized (PushReceivedJob.RECEIVE_LOCK) {
|
synchronized (PushReceivedJob.RECEIVE_LOCK) {
|
||||||
receiver.retrieveMessages(envelope -> {
|
receiver.retrieveMessages(envelope -> {
|
||||||
Log.i(tag, "Retrieved an envelope." + timeSuffix(startTime));
|
Log.i(tag, "Retrieved an envelope." + timeSuffix(startTime));
|
||||||
processEnvelope(envelope);
|
processEnvelope(envelope, false);
|
||||||
Log.i(tag, "Successfully processed an envelope." + timeSuffix(startTime));
|
Log.i(tag, "Successfully processed an envelope." + timeSuffix(startTime));
|
||||||
});
|
});
|
||||||
TextSecurePreferences.setNeedsMessagePull(context, false);
|
TextSecurePreferences.setNeedsMessagePull(context, false);
|
||||||
|
@ -22,7 +22,7 @@ public abstract class PushReceivedJob extends BaseJob {
|
|||||||
super(parameters);
|
super(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processEnvelope(@NonNull SignalServiceEnvelope envelope) {
|
public void processEnvelope(@NonNull SignalServiceEnvelope envelope, boolean isPushNotification) {
|
||||||
synchronized (RECEIVE_LOCK) {
|
synchronized (RECEIVE_LOCK) {
|
||||||
try {
|
try {
|
||||||
if (envelope.hasSource()) {
|
if (envelope.hasSource()) {
|
||||||
@ -37,7 +37,7 @@ public abstract class PushReceivedJob extends BaseJob {
|
|||||||
if (envelope.isReceipt()) {
|
if (envelope.isReceipt()) {
|
||||||
handleReceipt(envelope);
|
handleReceipt(envelope);
|
||||||
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender() || envelope.isFriendRequest()) {
|
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender() || envelope.isFriendRequest()) {
|
||||||
handleMessage(envelope);
|
handleMessage(envelope, isPushNotification);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
|
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
|
||||||
}
|
}
|
||||||
@ -47,8 +47,8 @@ public abstract class PushReceivedJob extends BaseJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMessage(SignalServiceEnvelope envelope) {
|
private void handleMessage(SignalServiceEnvelope envelope, boolean isPushNotification) {
|
||||||
new PushDecryptJob(context).processMessage(envelope);
|
new PushDecryptJob(context).processMessage(envelope, isPushNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
@SuppressLint("DefaultLocale")
|
||||||
|
@ -38,7 +38,7 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
|||||||
LokiAPI.configureIfNeeded(userHexEncodedPublicKey, lokiAPIDatabase, broadcaster)
|
LokiAPI.configureIfNeeded(userHexEncodedPublicKey, lokiAPIDatabase, broadcaster)
|
||||||
LokiAPI.shared.getMessages().map { messages ->
|
LokiAPI.shared.getMessages().map { messages ->
|
||||||
messages.forEach {
|
messages.forEach {
|
||||||
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it))
|
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
|
@ -24,7 +24,7 @@ class PushNotificationService : FirebaseMessagingService() {
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
try {
|
try {
|
||||||
val envelope = LokiMessageWrapper.unwrap(data)
|
val envelope = LokiMessageWrapper.unwrap(data)
|
||||||
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope))
|
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to unwrap data for message.")
|
Log.d("Loki", "Failed to unwrap data for message.")
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.loki.protocol
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import network.loki.messenger.BuildConfig
|
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
@ -34,21 +33,21 @@ object MultiDeviceProtocol {
|
|||||||
enum class MessageType { Text, Media }
|
enum class MessageType { Text, Media }
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun sendTextPush(context: Context, recipient: Recipient, messageID: Long) {
|
fun sendTextPush(context: Context, recipient: Recipient, messageID: Long, isEndSession: Boolean) {
|
||||||
sendMessagePush(context, recipient, messageID, MessageType.Text)
|
sendMessagePush(context, recipient, messageID, MessageType.Text, isEndSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun sendMediaPush(context: Context, recipient: Recipient, messageID: Long) {
|
fun sendMediaPush(context: Context, recipient: Recipient, messageID: Long) {
|
||||||
sendMessagePush(context, recipient, messageID, MessageType.Media)
|
sendMessagePush(context, recipient, messageID, MessageType.Media, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType): PushSendJob {
|
private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType, isEndSession: Boolean): PushSendJob {
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||||
val threadFRStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
val threadFRStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
||||||
val isNoteToSelf = SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())
|
val isNoteToSelf = SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())
|
||||||
val isContactFriend = (threadFRStatus == LokiThreadFriendRequestStatus.FRIENDS || isNoteToSelf) // In the note to self case the device linking request was the FR
|
val isContactFriend = (threadFRStatus == LokiThreadFriendRequestStatus.FRIENDS || isNoteToSelf) // In the note to self case the device linking request was the FR
|
||||||
val isFRMessage = !isContactFriend // Holds true assuming this method isn't invoked for control messages
|
val isFRMessage = !isContactFriend
|
||||||
val hasVisibleContent = when (messageType) {
|
val hasVisibleContent = when (messageType) {
|
||||||
MessageType.Text -> DatabaseFactory.getSmsDatabase(context).getMessage(messageID).body.isNotBlank()
|
MessageType.Text -> DatabaseFactory.getSmsDatabase(context).getMessage(messageID).body.isNotBlank()
|
||||||
MessageType.Media -> {
|
MessageType.Media -> {
|
||||||
@ -56,10 +55,9 @@ object MultiDeviceProtocol {
|
|||||||
outgoingMediaMessage.body.isNotBlank() || outgoingMediaMessage.attachments.isNotEmpty()
|
outgoingMediaMessage.body.isNotBlank() || outgoingMediaMessage.attachments.isNotEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isFRMessage && !hasVisibleContent && BuildConfig.DEBUG) { throw IllegalStateException() } // Verify the above assumption
|
|
||||||
val shouldSendAutoGeneratedFR = !isContactFriend && !isFRMessage
|
val shouldSendAutoGeneratedFR = !isContactFriend && !isFRMessage
|
||||||
&& !isNoteToSelf && !recipient.address.isGroup // Group threads work through session requests
|
&& !isNoteToSelf && !recipient.address.isGroup // Group threads work through session requests
|
||||||
&& hasVisibleContent
|
&& hasVisibleContent && !isEndSession
|
||||||
if (!shouldSendAutoGeneratedFR) {
|
if (!shouldSendAutoGeneratedFR) {
|
||||||
when (messageType) {
|
when (messageType) {
|
||||||
MessageType.Text -> return PushTextSendJob(messageID, messageID, recipient.address, isFRMessage, null)
|
MessageType.Text -> return PushTextSendJob(messageID, messageID, recipient.address, isFRMessage, null)
|
||||||
@ -74,7 +72,7 @@ object MultiDeviceProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType) {
|
private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType, isEndSession: Boolean) {
|
||||||
val jobManager = ApplicationContext.getInstance(context).jobManager
|
val jobManager = ApplicationContext.getInstance(context).jobManager
|
||||||
val isMultiDeviceRequired = !recipient.address.isOpenGroup
|
val isMultiDeviceRequired = !recipient.address.isOpenGroup
|
||||||
if (!isMultiDeviceRequired) {
|
if (!isMultiDeviceRequired) {
|
||||||
@ -86,7 +84,7 @@ object MultiDeviceProtocol {
|
|||||||
val publicKey = recipient.address.serialize()
|
val publicKey = recipient.address.serialize()
|
||||||
LokiFileServerAPI.shared.getDeviceLinks(publicKey).success {
|
LokiFileServerAPI.shared.getDeviceLinks(publicKey).success {
|
||||||
val devices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
val devices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
||||||
val jobs = devices.map { sendMessagePushToDevice(context, recipient(context, it), messageID, messageType) }
|
val jobs = devices.map { sendMessagePushToDevice(context, recipient(context, it), messageID, messageType, isEndSession) }
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
when (messageType) {
|
when (messageType) {
|
||||||
MessageType.Text -> jobManager.startChain(jobs).enqueue()
|
MessageType.Text -> jobManager.startChain(jobs).enqueue()
|
||||||
|
@ -89,7 +89,6 @@ object SessionManagementProtocol {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun handleEndSessionMessageIfNeeded(context: Context, content: SignalServiceContent) {
|
fun handleEndSessionMessageIfNeeded(context: Context, content: SignalServiceContent) {
|
||||||
if (!content.dataMessage.isPresent || !content.dataMessage.get().isEndSession) { return }
|
if (!content.dataMessage.isPresent || !content.dataMessage.get().isEndSession) { return }
|
||||||
// TODO: Notify the user
|
|
||||||
val sessionStore = TextSecureSessionStore(context)
|
val sessionStore = TextSecureSessionStore(context)
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||||
Log.d("Loki", "Received a session reset request from: ${content.sender}; archiving the session.")
|
Log.d("Loki", "Received a session reset request from: ${content.sender}; archiving the session.")
|
||||||
|
@ -12,15 +12,13 @@ import android.support.annotation.Nullable;
|
|||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver;
|
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@ -33,6 +31,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
public class IncomingMessageObserver implements InjectableType, ConstraintObserver.Notifier {
|
public class IncomingMessageObserver implements InjectableType, ConstraintObserver.Notifier {
|
||||||
|
|
||||||
private static final String TAG = IncomingMessageObserver.class.getSimpleName();
|
private static final String TAG = IncomingMessageObserver.class.getSimpleName();
|
||||||
@ -159,7 +159,7 @@ public class IncomingMessageObserver implements InjectableType, ConstraintObserv
|
|||||||
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
|
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
|
||||||
envelope -> {
|
envelope -> {
|
||||||
Log.i(TAG, "Retrieved envelope! " + String.valueOf(envelope.getSource()));
|
Log.i(TAG, "Retrieved envelope! " + String.valueOf(envelope.getSource()));
|
||||||
new PushContentReceiveJob(context).processEnvelope(envelope);
|
new PushContentReceiveJob(context).processEnvelope(envelope, false);
|
||||||
});
|
});
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
Log.w(TAG, "Application level read timeout...");
|
Log.w(TAG, "Application level read timeout...");
|
||||||
|
@ -81,7 +81,7 @@ public class MessageSender {
|
|||||||
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageId, allocatedThreadId);
|
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageId, allocatedThreadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
|
sendTextMessage(context, recipient, forceSms, keyExchange, messageId, message.isEndSession());
|
||||||
|
|
||||||
return allocatedThreadId;
|
return allocatedThreadId;
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ public class MessageSender {
|
|||||||
if (messageRecord.isMms()) {
|
if (messageRecord.isMms()) {
|
||||||
sendMediaMessage(context, recipient, forceSms, messageId, expiresIn);
|
sendMediaMessage(context, recipient, forceSms, messageId, expiresIn);
|
||||||
} else {
|
} else {
|
||||||
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
|
sendTextMessage(context, recipient, forceSms, keyExchange, messageId, messageRecord.isEndSession());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,17 +152,17 @@ public class MessageSender {
|
|||||||
|
|
||||||
private static void sendTextMessage(Context context, Recipient recipient,
|
private static void sendTextMessage(Context context, Recipient recipient,
|
||||||
boolean forceSms, boolean keyExchange,
|
boolean forceSms, boolean keyExchange,
|
||||||
long messageId)
|
long messageId, boolean isEndSession)
|
||||||
{
|
{
|
||||||
if (isLocalSelfSend(context, recipient, forceSms)) {
|
if (isLocalSelfSend(context, recipient, forceSms)) {
|
||||||
sendLocalTextSelf(context, messageId);
|
sendLocalTextSelf(context, messageId);
|
||||||
} else {
|
} else {
|
||||||
sendTextPush(context, recipient, messageId);
|
sendTextPush(context, recipient, messageId, isEndSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendTextPush(Context context, Recipient recipient, long messageId) {
|
private static void sendTextPush(Context context, Recipient recipient, long messageId, boolean isEndSession) {
|
||||||
MultiDeviceProtocol.sendTextPush(context, recipient, messageId);
|
MultiDeviceProtocol.sendTextPush(context, recipient, messageId, isEndSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
|
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
|
||||||
|
Loading…
Reference in New Issue
Block a user