@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?colorControlNormal"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:autoMirrored="false">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M120,800L120,160L692,400Q689,400 686,400Q683,400 680,400Q645,400 614,408Q583,416 554,430L200,280L200,420L440,480L200,540L200,680L416,588Q408,611 404,633.5Q400,656 400,680Q400,680 400,681Q400,682 400,682L120,800ZM680,880Q597,880 538.5,821.5Q480,763 480,680Q480,597 538.5,538.5Q597,480 680,480Q763,480 821.5,538.5Q880,597 880,680Q880,763 821.5,821.5Q763,880 680,880ZM746,774L774,746L700,672L700,560L660,560L660,688L746,774ZM200,588Q200,540 200,498Q200,456 200,430L200,280L200,420L200,420L200,540L200,540L200,680L200,588Z"/>
+</vector>
@@ -1069,6 +1069,8 @@ public class XmppConnectionService extends Service {
}
final var extras = intent == null ? null : intent.getExtras();
try {
+ Log.d(Config.LOGTAG, "looking for and sending scheduled messages");
+ sendScheduledMessages();
internalPingExecutor.execute(() -> manageAccountConnectionStates(action, extras));
} catch (final RejectedExecutionException e) {
Log.e(Config.LOGTAG, "can not schedule connection states manager");
@@ -1154,6 +1156,26 @@ public class XmppConnectionService extends Service {
WakeLockHelper.release(wakeLock);
}
+ private void sendScheduledMessages() {
+ List<Conversation> conversations = getConversations();
+ for (Conversation conversation : conversations) {
+ final Account account = conversation.getAccount();
+ final boolean inProgressJoin;
+ synchronized (account.inProgressConferenceJoins) {
+ inProgressJoin = account.inProgressConferenceJoins.contains(conversation);
+ }
+ final boolean pendingJoin;
+ synchronized (account.pendingConferenceJoins) {
+ pendingJoin = account.pendingConferenceJoins.contains(conversation);
+ }
+ if (conversation.getAccount() == account
+ && !pendingJoin
+ && !inProgressJoin) {
+ sendUnsentMessages(conversation);
+ }
+ }
+ }
+
private void handleOrbotStartedEvent() {
for (final Account account : accounts) {
if (account.getStatus() == Account.State.TOR_NOT_AVAILABLE) {
@@ -1830,9 +1852,19 @@ public class XmppConnectionService extends Service {
}
}
- @TargetApi(Build.VERSION_CODES.M)
private void scheduleNextIdlePing() {
- final long timeToWake = SystemClock.elapsedRealtime() + (Config.IDLE_PING_INTERVAL * 1000);
+ scheduleNextIdlePing(0);
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private void scheduleNextIdlePing(long epochMillis) {
+ final long timeToWake;
+ if (epochMillis > 0) {
+ timeToWake = epochMillis;
+ Log.d(Config.LOGTAG, "scheduling next idle ping for " + timeToWake);
+ } else {
+ timeToWake = SystemClock.elapsedRealtime() + (Config.IDLE_PING_INTERVAL * 1000);
+ }
final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
if (alarmManager == null) {
return;
@@ -2027,7 +2059,8 @@ public class XmppConnectionService extends Service {
}
}
- if (account.isOnlineAndConnected() && !inProgressJoin && !waitForPreview) {
+ // TODO: use timer to grab list of currently scheduled messages from in mem representation and sends them
+ if (account.isOnlineAndConnected() && !inProgressJoin && !waitForPreview && !(message.getTimeSent() > System.currentTimeMillis())) {
switch (message.getEncryption()) {
case Message.ENCRYPTION_NONE:
if (message.needsUploading()) {
@@ -2121,6 +2154,10 @@ public class XmppConnectionService extends Service {
message.setCounterpart(conversation.getMucOptions().getSelf().getFullJid());
}
+ if (message.getTimeSent() > System.currentTimeMillis()) {
+ scheduleNextIdlePing(message.getTimeSent());
+ }
+
if (resend) {
if (packet != null && addToConversation) {
if (account.getXmppConnection().getFeatures().sm() || mucMessage) {
@@ -12,9 +12,11 @@ import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
+import android.app.DatePickerDialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.PendingIntent;
+import android.app.TimePickerDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
@@ -23,6 +25,7 @@ import android.content.IntentSender.SendIntentException;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.icu.util.Calendar;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -928,6 +931,10 @@ public class ConversationFragment extends XmppFragment
}
private void sendMessage() {
+ sendMessage((Long) null);
+ }
+
+ private void sendMessage(Long sendAt) {
if (mediaPreviewAdapter.hasAttachments()) {
commitAttachments();
return;
@@ -998,6 +1005,7 @@ public class ConversationFragment extends XmppFragment
message.setServerMsgId(null);
message.setUuid(UUID.randomUUID().toString());
}
+ if (sendAt != null) message.setTime(sendAt);
switch (conversation.getNextEncryption()) {
case Message.ENCRYPTION_PGP:
sendPgpMessage(message);
@@ -1934,6 +1942,9 @@ public class ConversationFragment extends XmppFragment
case R.id.attach_subject:
binding.textinputSubject.setVisibility(binding.textinputSubject.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
break;
+ case R.id.attach_schedule:
+ scheduleMessage();
+ break;
case R.id.action_search:
startSearch();
break;
@@ -2011,6 +2022,23 @@ public class ConversationFragment extends XmppFragment
startActivity(intent);
}
+ private void scheduleMessage() {
+ // TODO: also gate menu option in UI behind version check
+ // TODO: upgrade to material you/3
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ final Calendar now = Calendar.getInstance();
+ new DatePickerDialog(activity, (view, year, month, day) -> {
+ new TimePickerDialog(activity, (view1, hour, minute) -> {
+ final Calendar c = Calendar.getInstance();
+ c.set(year, month, day, hour, minute);
+ final long timestamp = c.getTimeInMillis();
+ sendMessage(timestamp);
+ Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": scheduled message for " + timestamp);
+ }, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), true).show();
+ }, now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH)).show();
+ }
+ }
+
private void returnToOngoingCall() {
final Optional<OngoingRtpSession> ongoingRtpSession =
activity.xmppConnectionService