2018-10-29 22:14:31 +00:00
|
|
|
package org.thoughtcrime.securesms.components;
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.support.annotation.NonNull;
|
|
|
|
|
|
|
|
import org.thoughtcrime.securesms.ApplicationContext;
|
2019-10-06 23:25:14 +00:00
|
|
|
import org.thoughtcrime.securesms.database.Address;
|
|
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
|
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
2018-10-29 22:14:31 +00:00
|
|
|
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
2020-05-11 06:54:31 +00:00
|
|
|
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
2019-10-06 23:25:14 +00:00
|
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
2018-10-29 22:14:31 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2020-05-11 06:54:31 +00:00
|
|
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
|
2018-10-29 22:14:31 +00:00
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
2020-05-11 06:54:31 +00:00
|
|
|
import java.util.Set;
|
2018-10-29 22:14:31 +00:00
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
@SuppressLint("UseSparseArrays")
|
|
|
|
public class TypingStatusSender {
|
|
|
|
|
|
|
|
private static final String TAG = TypingStatusSender.class.getSimpleName();
|
|
|
|
|
|
|
|
private static final long REFRESH_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
|
|
|
private static final long PAUSE_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(3);
|
|
|
|
|
|
|
|
private final Context context;
|
|
|
|
private final Map<Long, TimerPair> selfTypingTimers;
|
|
|
|
|
|
|
|
public TypingStatusSender(@NonNull Context context) {
|
|
|
|
this.context = context;
|
|
|
|
this.selfTypingTimers = new HashMap<>();
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void onTypingStarted(long threadId) {
|
|
|
|
TimerPair pair = Util.getOrDefault(selfTypingTimers, threadId, new TimerPair());
|
|
|
|
selfTypingTimers.put(threadId, pair);
|
|
|
|
|
|
|
|
if (pair.getStart() == null) {
|
|
|
|
sendTyping(threadId, true);
|
|
|
|
|
|
|
|
Runnable start = new StartRunnable(threadId);
|
|
|
|
Util.runOnMainDelayed(start, REFRESH_TYPING_TIMEOUT);
|
|
|
|
pair.setStart(start);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pair.getStop() != null) {
|
|
|
|
Util.cancelRunnableOnMain(pair.getStop());
|
|
|
|
}
|
|
|
|
|
|
|
|
Runnable stop = () -> onTypingStopped(threadId, true);
|
|
|
|
Util.runOnMainDelayed(stop, PAUSE_TYPING_TIMEOUT);
|
|
|
|
pair.setStop(stop);
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void onTypingStopped(long threadId) {
|
|
|
|
onTypingStopped(threadId, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private synchronized void onTypingStopped(long threadId, boolean notify) {
|
|
|
|
TimerPair pair = Util.getOrDefault(selfTypingTimers, threadId, new TimerPair());
|
|
|
|
selfTypingTimers.put(threadId, pair);
|
|
|
|
|
|
|
|
if (pair.getStart() != null) {
|
|
|
|
Util.cancelRunnableOnMain(pair.getStart());
|
|
|
|
|
|
|
|
if (notify) {
|
|
|
|
sendTyping(threadId, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pair.getStop() != null) {
|
|
|
|
Util.cancelRunnableOnMain(pair.getStop());
|
|
|
|
}
|
|
|
|
|
|
|
|
pair.setStart(null);
|
|
|
|
pair.setStop(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sendTyping(long threadId, boolean typingStarted) {
|
2019-10-06 23:25:14 +00:00
|
|
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
2019-10-08 03:10:16 +00:00
|
|
|
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
|
2020-05-12 01:26:44 +00:00
|
|
|
// Loki - Check whether we want to send a typing indicator to this user
|
2020-05-11 06:54:31 +00:00
|
|
|
if (!SessionMetaProtocol.shouldSendTypingIndicator(recipient, context)) { return; }
|
2020-05-12 01:26:44 +00:00
|
|
|
// Loki - Take into account multi device
|
2020-05-11 06:54:31 +00:00
|
|
|
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize());
|
|
|
|
for (String device : linkedDevices) {
|
|
|
|
Recipient deviceAsRecipient = Recipient.from(context, Address.fromSerialized(device), false);
|
2020-05-12 01:26:44 +00:00
|
|
|
long deviceThreadID = threadDatabase.getThreadIdFor(deviceAsRecipient);
|
2020-05-11 06:54:31 +00:00
|
|
|
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted));
|
2019-10-06 23:25:14 +00:00
|
|
|
}
|
2018-10-29 22:14:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private class StartRunnable implements Runnable {
|
|
|
|
|
|
|
|
private final long threadId;
|
|
|
|
|
|
|
|
private StartRunnable(long threadId) {
|
|
|
|
this.threadId = threadId;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
sendTyping(threadId, true);
|
|
|
|
Util.runOnMainDelayed(this, REFRESH_TYPING_TIMEOUT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class TimerPair {
|
|
|
|
private Runnable start;
|
|
|
|
private Runnable stop;
|
|
|
|
|
|
|
|
public Runnable getStart() {
|
|
|
|
return start;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setStart(Runnable start) {
|
|
|
|
this.start = start;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Runnable getStop() {
|
|
|
|
return stop;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setStop(Runnable stop) {
|
|
|
|
this.stop = stop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|