Ensure proper group call history in chat after being offline.

Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
Cody Henthorne 2020-12-07 17:27:35 -05:00 committed by GitHub
parent 12330b0aff
commit dc4ce234b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 282 additions and 13 deletions

View file

@ -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<UUID> 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<UUID> peekJoinedUuids, boolean isCallFull);
public abstract Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type);

View file

@ -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<UUID> 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<UUID> peekJoinedUuids, boolean isCallFull) {
throw new UnsupportedOperationException();

View file

@ -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<UUID> 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<UUID> peekJoinedUuids, boolean isCallFull) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();

View file

@ -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<GroupCallPeekJob> {
@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));
}
}
}

View file

@ -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<GroupCallPeekWorkerJob> {
@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));
}
}
}

View file

@ -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());

View file

@ -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,

View file

@ -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));