diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index 691b141bd..c6e48f5df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -135,10 +135,13 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, @NonNull RecipientId sender, long timestamp, - @Nullable String messageGroupCallEraId, @Nullable String peekGroupCallEraId, @NonNull Collection peekJoinedUuids, boolean isCallFull); + public abstract void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, + @NonNull RecipientId sender, + long timestamp, + @Nullable String messageGroupCallEraId); public abstract boolean updatePreviousGroupCall(long threadId, @Nullable String peekGroupCallEraId, @NonNull Collection peekJoinedUuids, boolean isCallFull); public abstract Optional insertMessageInbox(IncomingTextMessage message, long type); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 3666e9071..abc99a611 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -466,7 +466,6 @@ public class MmsDatabase extends MessageDatabase { public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, @NonNull RecipientId sender, long timestamp, - @Nullable String messageGroupCallEraId, @Nullable String peekGroupCallEraId, @NonNull Collection peekJoinedUuids, boolean isCallFull) @@ -474,6 +473,15 @@ public class MmsDatabase extends MessageDatabase { throw new UnsupportedOperationException(); } + @Override + public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, + @NonNull RecipientId sender, + long timestamp, + @Nullable String messageGroupCallEraId) + { + throw new UnsupportedOperationException(); + } + @Override public boolean updatePreviousGroupCall(long threadId, @Nullable String peekGroupCallEraId, @NonNull Collection peekJoinedUuids, boolean isCallFull) { throw new UnsupportedOperationException(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 8088ae034..41d1d14ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -688,7 +688,6 @@ public class SmsDatabase extends MessageDatabase { public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, @NonNull RecipientId sender, long timestamp, - @Nullable String messageGroupCallEraId, @Nullable String peekGroupCallEraId, @NonNull Collection peekJoinedUuids, boolean isCallFull) @@ -747,6 +746,83 @@ public class SmsDatabase extends MessageDatabase { ApplicationDependencies.getJobManager().add(new TrimThreadJob(threadId)); } + @Override + public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, + @NonNull RecipientId sender, + long timestamp, + @Nullable String messageGroupCallEraId) + { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + + long threadId; + + try { + db.beginTransaction(); + + Recipient recipient = Recipient.resolved(groupRecipientId); + + threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); + + String where = TYPE + " = ? AND " + THREAD_ID + " = ?"; + String[] args = SqlUtil.buildArgs(Types.GROUP_CALL_TYPE, threadId); + boolean sameEraId = false; + + try (Reader reader = new Reader(db.query(TABLE_NAME, MESSAGE_PROJECTION, where, args, null, null, DATE_RECEIVED + " DESC", "1"))) { + MessageRecord record = reader.getNext(); + if (record != null) { + GroupCallUpdateDetails groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(record.getBody()); + + sameEraId = groupCallUpdateDetails.getEraId().equals(messageGroupCallEraId) && !Util.isEmpty(messageGroupCallEraId); + + if (!sameEraId) { + String body = GroupCallUpdateDetailsUtil.createUpdatedBody(groupCallUpdateDetails, Collections.emptyList(), false); + + ContentValues contentValues = new ContentValues(); + contentValues.put(BODY, body); + + db.update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(record.getId())); + } + } + } + + if (!sameEraId && !Util.isEmpty(messageGroupCallEraId)) { + byte[] updateDetails = GroupCallUpdateDetails.newBuilder() + .setEraId(Util.emptyIfNull(messageGroupCallEraId)) + .setStartedCallUuid(Recipient.resolved(sender).requireUuid().toString()) + .setStartedCallTimestamp(timestamp) + .addAllInCallUuids(Collections.emptyList()) + .setIsCallFull(false) + .build() + .toByteArray(); + + String body = Base64.encodeBytes(updateDetails); + + ContentValues values = new ContentValues(); + values.put(RECIPIENT_ID, sender.serialize()); + values.put(ADDRESS_DEVICE_ID, 1); + values.put(DATE_RECEIVED, timestamp); + values.put(DATE_SENT, timestamp); + values.put(READ, 0); + values.put(BODY, body); + values.put(TYPE, Types.GROUP_CALL_TYPE); + values.put(THREAD_ID, threadId); + + db.insert(TABLE_NAME, null, values); + + DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); + } + + DatabaseFactory.getThreadDatabase(context).update(threadId, true); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + notifyConversationListeners(threadId); + ApplicationDependencies.getJobManager().add(new TrimThreadJob(threadId)); + } + @Override public boolean updatePreviousGroupCall(long threadId, @Nullable String peekGroupCallEraId, @NonNull Collection peekJoinedUuids, boolean isCallFull) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekJob.java new file mode 100644 index 000000000..36b0f2c96 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekJob.java @@ -0,0 +1,89 @@ +package org.thoughtcrime.securesms.jobs; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobmanager.impl.WebsocketDrainedConstraint; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.service.webrtc.WebRtcData; + +/** + * Allows the enqueueing of one peek operation per group while the web socket is not drained. + */ +public final class GroupCallPeekJob extends BaseJob { + + public static final String KEY = "GroupCallPeekJob"; + + private static final String QUEUE = "__GroupCallPeekJob__"; + + private static final String KEY_SENDER = "sender"; + private static final String KEY_GROUP_RECIPIENT_ID = "group_recipient_id"; + private static final String KEY_GROUP_CALL_ERA_ID = "group_call_era_id"; + private static final String KEY_SERVER_RECEIVED_TIMESTAMP = "server_timestamp"; + + @NonNull private final WebRtcData.GroupCallUpdateMetadata updateMetadata; + + public static void enqueue(@NonNull WebRtcData.GroupCallUpdateMetadata updateMetadata) { + JobManager jobManager = ApplicationDependencies.getJobManager(); + String queue = QUEUE + updateMetadata.getGroupRecipientId().serialize(); + Parameters.Builder parameters = new Parameters.Builder() + .setQueue(queue) + .addConstraint(WebsocketDrainedConstraint.KEY); + + jobManager.cancelAllInQueue(queue); + + jobManager.add(new GroupCallPeekJob(parameters.build(), updateMetadata)); + } + + private GroupCallPeekJob(@NonNull Parameters parameters, + @NonNull WebRtcData.GroupCallUpdateMetadata updateMetadata) + { + super(parameters); + this.updateMetadata = updateMetadata; + } + + @Override + protected void onRun() { + ApplicationDependencies.getJobManager().add(new GroupCallPeekWorkerJob(updateMetadata)); + } + + @Override + protected boolean onShouldRetry(@NonNull Exception e) { + return false; + } + + @Override + public @NonNull Data serialize() { + return new Data.Builder() + .putString(KEY_SENDER, updateMetadata.getSender().serialize()) + .putString(KEY_GROUP_RECIPIENT_ID, updateMetadata.getGroupRecipientId().serialize()) + .putString(KEY_GROUP_CALL_ERA_ID, updateMetadata.getGroupCallEraId()) + .putLong(KEY_SERVER_RECEIVED_TIMESTAMP, updateMetadata.getServerReceivedTimestamp()) + .build(); + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void onFailure() { + } + + public static final class Factory implements Job.Factory { + + @Override + public @NonNull GroupCallPeekJob create(@NonNull Parameters parameters, @NonNull Data data) { + RecipientId sender = RecipientId.from(data.getString(KEY_SENDER)); + RecipientId group = RecipientId.from(data.getString(KEY_GROUP_RECIPIENT_ID)); + String era = data.getString(KEY_GROUP_CALL_ERA_ID); + long serverTimestamp = data.getLong(KEY_SERVER_RECEIVED_TIMESTAMP); + + return new GroupCallPeekJob(parameters, new WebRtcData.GroupCallUpdateMetadata(sender, group, era, serverTimestamp)); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekWorkerJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekWorkerJob.java new file mode 100644 index 000000000..1c71a14b8 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekWorkerJob.java @@ -0,0 +1,90 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Intent; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.service.WebRtcCallService; +import org.thoughtcrime.securesms.service.webrtc.WebRtcData; + +/** + * Runs in the same queue as messages for the group. + */ +final class GroupCallPeekWorkerJob extends BaseJob { + + public static final String KEY = "GroupCallPeekWorkerJob"; + + private static final String KEY_SENDER = "sender"; + private static final String KEY_GROUP_RECIPIENT_ID = "group_recipient_id"; + private static final String KEY_GROUP_CALL_ERA_ID = "group_call_era_id"; + private static final String KEY_SERVER_RECEIVED_TIMESTAMP = "server_timestamp"; + + @NonNull private final WebRtcData.GroupCallUpdateMetadata updateMetadata; + + public GroupCallPeekWorkerJob(@NonNull WebRtcData.GroupCallUpdateMetadata updateMetadata) { + this(new Parameters.Builder() + .setQueue(PushProcessMessageJob.getQueueName(updateMetadata.getGroupRecipientId())) + .build(), + updateMetadata); + } + + private GroupCallPeekWorkerJob(@NonNull Parameters parameters, + @NonNull WebRtcData.GroupCallUpdateMetadata updateMetadata) + { + super(parameters); + this.updateMetadata = updateMetadata; + } + + @Override + protected void onRun() { + Intent intent = new Intent(context, WebRtcCallService.class); + + intent.setAction(WebRtcCallService.ACTION_GROUP_CALL_UPDATE_MESSAGE) + .putExtra(WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_SENDER, updateMetadata.getSender().serialize()) + .putExtra(WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_GROUP, updateMetadata.getGroupRecipientId().serialize()) + .putExtra(WebRtcCallService.EXTRA_GROUP_CALL_ERA_ID, updateMetadata.getGroupCallEraId()) + .putExtra(WebRtcCallService.EXTRA_SERVER_RECEIVED_TIMESTAMP, updateMetadata.getServerReceivedTimestamp()); + + context.startService(intent); + } + + @Override + protected boolean onShouldRetry(@NonNull Exception e) { + return false; + } + + @Override + public @NonNull Data serialize() { + return new Data.Builder() + .putString(KEY_SENDER, updateMetadata.getSender().serialize()) + .putString(KEY_GROUP_RECIPIENT_ID, updateMetadata.getGroupRecipientId().serialize()) + .putString(KEY_GROUP_CALL_ERA_ID, updateMetadata.getGroupCallEraId()) + .putLong(KEY_SERVER_RECEIVED_TIMESTAMP, updateMetadata.getServerReceivedTimestamp()) + .build(); + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void onFailure() { + } + + public static final class Factory implements Job.Factory { + + @Override + public @NonNull GroupCallPeekWorkerJob create(@NonNull Parameters parameters, @NonNull Data data) { + RecipientId sender = RecipientId.from(data.getString(KEY_SENDER)); + RecipientId group = RecipientId.from(data.getString(KEY_GROUP_RECIPIENT_ID)); + String era = data.getString(KEY_GROUP_CALL_ERA_ID); + long serverTimestamp = data.getLong(KEY_SERVER_RECEIVED_TIMESTAMP); + + return new GroupCallPeekWorkerJob(parameters, new WebRtcData.GroupCallUpdateMetadata(sender, group, era, serverTimestamp)); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index d87275a4f..d8d2c1b30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -72,6 +72,8 @@ public final class JobManagerFactories { put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory()); put(GroupV1MigrationJob.KEY, new GroupV1MigrationJob.Factory()); put(GroupCallUpdateSendJob.KEY, new GroupCallUpdateSendJob.Factory()); + put(GroupCallPeekJob.KEY, new GroupCallPeekJob.Factory()); + put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory()); put(KbsEnclaveMigrationWorkerJob.KEY, new KbsEnclaveMigrationWorkerJob.Factory()); put(LeaveGroupJob.KEY, new LeaveGroupJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index ca841057c..d0042da4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -74,6 +74,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.WebRtcCallService; +import org.thoughtcrime.securesms.service.webrtc.WebRtcData; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -661,17 +662,19 @@ public final class PushProcessMessageJob extends BaseJob { return; } - RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromPossiblyMigratedGroupId(groupId.get()); + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromPossiblyMigratedGroupId(groupId.get()); + WebRtcData.GroupCallUpdateMetadata updateMetadata = new WebRtcData.GroupCallUpdateMetadata(RecipientId.from(content.getSender()), + groupRecipientId, + message.getGroupCallUpdate().get().getEraId(), + content.getServerReceivedTimestamp()); - Intent intent = new Intent(context, WebRtcCallService.class); + DatabaseFactory.getSmsDatabase(context).insertOrUpdateGroupCall(updateMetadata.getGroupRecipientId(), + updateMetadata.getSender(), + updateMetadata.getServerReceivedTimestamp(), + updateMetadata.getGroupCallEraId()); - intent.setAction(WebRtcCallService.ACTION_GROUP_CALL_UPDATE_MESSAGE) - .putExtra(WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_SENDER, RecipientId.from(content.getSender()).serialize()) - .putExtra(WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_GROUP, groupRecipientId.serialize()) - .putExtra(WebRtcCallService.EXTRA_GROUP_CALL_ERA_ID, message.getGroupCallUpdate().get().getEraId()) - .putExtra(WebRtcCallService.EXTRA_SERVER_RECEIVED_TIMESTAMP, content.getServerReceivedTimestamp()); - context.startService(intent); + GroupCallPeekJob.enqueue(updateMetadata); } private void handleEndSessionMessage(@NonNull SignalServiceContent content, diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java index 01ccba07f..faf0b76d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -715,7 +715,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, DatabaseFactory.getSmsDatabase(this).insertOrUpdateGroupCall(group.getId(), groupCallUpdateMetadata.getSender(), groupCallUpdateMetadata.getServerReceivedTimestamp(), - groupCallUpdateMetadata.getGroupCallEraId(), peekInfo.getEraId(), peekInfo.getJoinedMembers(), WebRtcUtil.isCallFull(peekInfo)); @@ -766,7 +765,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getSmsDatabase(this).insertOrUpdateGroupCall(groupId, Recipient.self().getId(), System.currentTimeMillis(), - null, groupCallEraId, joinedMembers, isCallFull));