From 31e137cf6d9a10e7d313530a8ab6f1e4878cf692 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 4 Nov 2020 10:42:45 -0400 Subject: [PATCH] Add support for MISSED_VIDEO_CALL type. --- .../ConversationListItem.java | 6 ++++-- .../securesms/database/MessageDatabase.java | 4 ++-- .../securesms/database/MmsDatabase.java | 4 ++-- .../securesms/database/MmsSmsColumns.java | 13 ++++++++---- .../securesms/database/SmsDatabase.java | 21 ++++++++++--------- .../database/model/DisplayRecord.java | 8 +++++-- .../database/model/MessageRecord.java | 6 ++++-- .../securesms/jobs/PushProcessMessageJob.java | 4 ++-- .../securesms/service/WebRtcCallService.java | 4 ++-- .../ActiveCallActionProcessorDelegate.java | 10 ++++----- .../webrtc/IncomingCallActionProcessor.java | 2 +- .../service/webrtc/WebRtcActionProcessor.java | 6 +++--- .../service/webrtc/WebRtcInteractor.java | 4 ++-- app/src/main/res/values/strings.xml | 6 ++++-- 14 files changed, 57 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index b6a73684f9..821ced5741 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -450,8 +450,10 @@ public final class ConversationListItem extends RelativeLayout return emphasisAdded(context, context.getString(R.string.ThreadRecord_called)); } else if (SmsDatabase.Types.isIncomingCall(thread.getType())) { return emphasisAdded(context, context.getString(R.string.ThreadRecord_called_you)); - } else if (SmsDatabase.Types.isMissedCall(thread.getType())) { - return emphasisAdded(context, context.getString(R.string.ThreadRecord_missed_call)); + } else if (SmsDatabase.Types.isMissedAudioCall(thread.getType())) { + return emphasisAdded(context, context.getString(R.string.ThreadRecord_missed_audio_call)); + } else if (SmsDatabase.Types.isMissedVideoCall(thread.getType())) { + return emphasisAdded(context, context.getString(R.string.ThreadRecord_missed_video_call)); } else if (SmsDatabase.Types.isJoinedType(thread.getType())) { return emphasisAdded(recipientToStringAsync(thread.getRecipient().getId(), r -> new SpannableString(context.getString(R.string.ThreadRecord_s_is_on_signal, r.getDisplayName(context))))); } else if (SmsDatabase.Types.isExpirationTimerUpdate(thread.getType())) { 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 f25e5b4c70..4e238171e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -108,7 +108,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract void markUnidentified(long messageId, boolean unidentified); public abstract void markAsSending(long messageId); public abstract void markAsRemoteDelete(long messageId); - public abstract void markAsMissedCall(long id); + public abstract void markAsMissedCall(long id, boolean isVideoOffer); public abstract void markAsNotified(long id); public abstract void markSmsStatus(long id, int status); public abstract void markDownloadState(long messageId, long state); @@ -126,7 +126,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract @NonNull Pair insertReceivedCall(@NonNull RecipientId address); public abstract @NonNull Pair insertOutgoingCall(@NonNull RecipientId address); - public abstract @NonNull Pair insertMissedCall(@NonNull RecipientId address, long timestamp); + public abstract @NonNull Pair insertMissedCall(@NonNull RecipientId address, long timestamp, boolean isVideoOffer); public abstract Optional insertMessageInbox(IncomingTextMessage message, long type); public abstract Optional insertMessageInbox(IncomingTextMessage message); 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 2c1162cf5b..98564e2837 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -365,7 +365,7 @@ public class MmsDatabase extends MessageDatabase { } @Override - public void markAsMissedCall(long id) { + public void markAsMissedCall(long id, boolean isVideoOffer) { throw new UnsupportedOperationException(); } @@ -390,7 +390,7 @@ public class MmsDatabase extends MessageDatabase { } @Override - public @NonNull Pair insertMissedCall(@NonNull RecipientId address, long timestamp) { + public @NonNull Pair insertMissedCall(@NonNull RecipientId address, long timestamp, boolean isVideoOffer) { throw new UnsupportedOperationException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 64bcae9236..bc848489ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -34,11 +34,12 @@ public interface MmsSmsColumns { protected static final long INCOMING_CALL_TYPE = 1; protected static final long OUTGOING_CALL_TYPE = 2; - protected static final long MISSED_CALL_TYPE = 3; + protected static final long MISSED_AUDIO_CALL_TYPE = 3; protected static final long JOINED_TYPE = 4; protected static final long UNSUPPORTED_MESSAGE_TYPE = 5; protected static final long INVALID_MESSAGE_TYPE = 6; protected static final long PROFILE_CHANGE_TYPE = 7; + protected static final long MISSED_VIDEO_CALL_TYPE = 8; protected static final long BASE_INBOX_TYPE = 20; protected static final long BASE_OUTBOX_TYPE = 21; @@ -204,7 +205,7 @@ public interface MmsSmsColumns { } public static boolean isCallLog(long type) { - return type == INCOMING_CALL_TYPE || type == OUTGOING_CALL_TYPE || type == MISSED_CALL_TYPE; + return type == INCOMING_CALL_TYPE || type == OUTGOING_CALL_TYPE || type == MISSED_AUDIO_CALL_TYPE || type == MISSED_VIDEO_CALL_TYPE; } public static boolean isExpirationTimerUpdate(long type) { @@ -219,8 +220,12 @@ public interface MmsSmsColumns { return type == OUTGOING_CALL_TYPE; } - public static boolean isMissedCall(long type) { - return type == MISSED_CALL_TYPE; + public static boolean isMissedAudioCall(long type) { + return type == MISSED_AUDIO_CALL_TYPE; + } + + public static boolean isMissedVideoCall(long type) { + return type == MISSED_VIDEO_CALL_TYPE; } public static boolean isGroupUpdate(long type) { 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 1a96ac39d0..eecd5adebe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -364,8 +364,8 @@ public class SmsDatabase extends MessageDatabase { } @Override - public void markAsMissedCall(long id) { - updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE); + public void markAsMissedCall(long id, boolean isVideoOffer) { + updateTypeBitmask(id, Types.TOTAL_MASK, isVideoOffer ? Types.MISSED_VIDEO_CALL_TYPE : Types.MISSED_AUDIO_CALL_TYPE); } @Override @@ -633,12 +633,13 @@ public class SmsDatabase extends MessageDatabase { @Override public boolean hasReceivedAnyCallsSince(long threadId, long timestamp) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); - String[] projection = new String[]{SmsDatabase.TYPE}; - String selection = THREAD_ID + " = ? AND " + DATE_RECEIVED + " > ? AND (" + TYPE + " = ? OR " + TYPE + " = ?)"; - String[] selectionArgs = new String[]{String.valueOf(threadId), - String.valueOf(timestamp), - String.valueOf(Types.INCOMING_CALL_TYPE), - String.valueOf(Types.MISSED_CALL_TYPE)}; + String[] projection = SqlUtil.buildArgs(SmsDatabase.TYPE); + String selection = THREAD_ID + " = ? AND " + DATE_RECEIVED + " > ? AND (" + TYPE + " = ? OR " + TYPE + " = ? OR " + TYPE + " =?)"; + String[] selectionArgs = SqlUtil.buildArgs(threadId, + timestamp, + Types.INCOMING_CALL_TYPE, + Types.MISSED_AUDIO_CALL_TYPE, + Types.MISSED_VIDEO_CALL_TYPE); try (Cursor cursor = db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, null)) { return cursor != null && cursor.moveToFirst(); @@ -656,8 +657,8 @@ public class SmsDatabase extends MessageDatabase { } @Override - public @NonNull Pair insertMissedCall(@NonNull RecipientId address, long timestamp) { - return insertCallLog(address, Types.MISSED_CALL_TYPE, true, timestamp); + public @NonNull Pair insertMissedCall(@NonNull RecipientId address, long timestamp, boolean isVideoCall) { + return insertCallLog(address, isVideoCall ? Types.MISSED_VIDEO_CALL_TYPE : Types.MISSED_AUDIO_CALL_TYPE, true, timestamp); } private @NonNull Pair insertCallLog(@NonNull RecipientId recipientId, long type, boolean unread, long timestamp) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index ef915fc60f..7f9d826af6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -148,8 +148,12 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isOutgoingCall(type); } - public boolean isMissedCall() { - return SmsDatabase.Types.isMissedCall(type); + public final boolean isMissedAudioCall() { + return SmsDatabase.Types.isMissedAudioCall(type); + } + + public final boolean isMissedVideoCall() { + return SmsDatabase.Types.isMissedVideoCall(type); } public boolean isVerificationStatusChange() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 95f8675a2e..71b3e7cac1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -144,8 +144,10 @@ public abstract class MessageRecord extends DisplayRecord { return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_s_called_you_date, r.getDisplayName(context), getCallDateString(context)), R.drawable.ic_update_audio_call_incoming_light_16, R.drawable.ic_update_audio_call_incoming_dark_16); } else if (isOutgoingCall()) { return staticUpdateDescription(context.getString(R.string.MessageRecord_you_called_date, getCallDateString(context)), R.drawable.ic_update_audio_call_outgoing_light_16, R.drawable.ic_update_audio_call_outgoing_dark_16); - } else if (isMissedCall()) { - return staticUpdateDescription(context.getString(R.string.MessageRecord_missed_call_date, getCallDateString(context)), R.drawable.ic_update_audio_call_missed_light_16, R.drawable.ic_update_audio_call_missed_dark_16, ContextCompat.getColor(context, R.color.core_red_shade), ContextCompat.getColor(context, R.color.core_red)); + } else if (isMissedAudioCall()) { + return staticUpdateDescription(context.getString(R.string.MessageRecord_missed_audio_call_date, getCallDateString(context)), R.drawable.ic_update_audio_call_missed_light_16, R.drawable.ic_update_audio_call_missed_dark_16, ContextCompat.getColor(context, R.color.core_red_shade), ContextCompat.getColor(context, R.color.core_red)); + } else if (isMissedVideoCall()) { + return staticUpdateDescription(context.getString(R.string.MessageRecord_missed_video_call_date, getCallDateString(context)), R.drawable.ic_update_video_call_missed_light_16, R.drawable.ic_update_video_call_missed_dark_16, ContextCompat.getColor(context, R.color.core_red_shade), ContextCompat.getColor(context, R.color.core_red)); } else if (isJoined()) { return staticUpdateDescription(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().getDisplayName(context)), R.drawable.ic_update_group_add_light_16, R.drawable.ic_update_group_add_dark_16); } else if (isExpirationTimerUpdate()) { 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 b4ecac2699..ec6f355213 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -506,7 +506,7 @@ public final class PushProcessMessageJob extends BaseJob { if (smsMessageId.isPresent()) { MessageDatabase database = DatabaseFactory.getSmsDatabase(context); - database.markAsMissedCall(smsMessageId.get()); + database.markAsMissedCall(smsMessageId.get(), message.getType() == OfferMessage.Type.VIDEO_CALL); } else { Intent intent = new Intent(context, WebRtcCallService.class); Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender()); @@ -581,7 +581,7 @@ public final class PushProcessMessageJob extends BaseJob { { log(TAG, String.valueOf(content), "handleCallHangupMessage"); if (smsMessageId.isPresent()) { - DatabaseFactory.getSmsDatabase(context).markAsMissedCall(smsMessageId.get()); + DatabaseFactory.getSmsDatabase(context).markAsMissedCall(smsMessageId.get(), false); } else { Intent intent = new Intent(context, WebRtcCallService.class); RemotePeer remotePeer = new RemotePeer(Recipient.externalHighTrustPush(context, content.getSender()).getId()); 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 2b7d3f0466..dc559d110a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -328,8 +328,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } } - public void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp) { - Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(remotePeer.getId(), timestamp); + public void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) { + Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(remotePeer.getId(), timestamp, isVideoOffer); ApplicationDependencies.getMessageNotifier().updateNotification(this, messageAndThreadId.second(), signal); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java index dd1eb1a5f7..61472ca4ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java @@ -178,7 +178,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { webRtcInteractor.stopForegroundService(); } - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp()); + webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); return terminate(currentState, remotePeer); } @@ -206,7 +206,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { } if (incomingBeforeAccept) { - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp()); + webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } } else if (action.equals(ACTION_ENDED_REMOTE_BUSY) && remotePeerIsActive) { activePeer.receivedBusy(); @@ -215,7 +215,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { ringer.start(OutgoingRinger.Type.BUSY); Util.runOnMainDelayed(ringer::stop, BUSY_TONE_LENGTH); } else if (action.equals(ACTION_ENDED_REMOTE_GLARE) && incomingBeforeAccept) { - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp()); + webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } currentState = currentState.builder() @@ -242,7 +242,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { } if (remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING) { - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp()); + webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } return terminate(currentState, remotePeer); @@ -273,7 +273,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { webRtcInteractor.sendMessage(currentState); if (activePeer.getState() == CallState.ANSWERING || activePeer.getState() == CallState.LOCAL_RINGING) { - webRtcInteractor.insertMissedCall(activePeer, true, activePeer.getCallStartTimestamp()); + webRtcInteractor.insertMissedCall(activePeer, true, activePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } return terminate(currentState, activePeer); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java index 615b0ff05f..06cc4c2e3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java @@ -136,7 +136,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { try { webRtcInteractor.getCallManager().hangup(); - DatabaseFactory.getSmsDatabase(context).insertMissedCall(activePeer.getId(), System.currentTimeMillis()); + DatabaseFactory.getSmsDatabase(context).insertMissedCall(activePeer.getId(), System.currentTimeMillis(), currentState.getCallSetupState().isRemoteVideoOffer()); return terminate(currentState, activePeer); } catch (CallException e) { return callFailure(currentState, "hangup() failed: ", e); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java index 00df72693e..518447dc74 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java @@ -284,14 +284,14 @@ public abstract class WebRtcActionProcessor { if (TelephonyUtil.isAnyPstnLineBusy(context)) { Log.i(tag, "PSTN line is busy."); currentState = currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true); - webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp()); + webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); return currentState; } if (!RecipientUtil.isCallRequestAccepted(context.getApplicationContext(), callMetadata.getRemotePeer().getRecipient())) { Log.w(tag, "Caller is untrusted."); currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NEED_PERMISSION), true); - webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp()); + webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); return currentState; } @@ -338,7 +338,7 @@ public abstract class WebRtcActionProcessor { { Log.i(tag, "handleReceivedOfferExpired(): call_id: " + remotePeer.getCallId()); - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp()); + webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); return terminate(currentState, remotePeer); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java index 64a7719b68..d6864949ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java @@ -85,8 +85,8 @@ public class WebRtcInteractor { webRtcCallService.stopForeground(true); } - void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp) { - webRtcCallService.insertMissedCall(remotePeer, signal, timestamp); + void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) { + webRtcCallService.insertMissedCall(remotePeer, signal, timestamp, isVideoOffer); } void startWebRtcCallActivityIfPossible() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 303c0492d7..06f9c15749 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -940,7 +940,8 @@ You updated the group. The group was updated. You called · %1$s - Missed call · %1$s + Missed audio call · %1$s + Missed video call · %1$s %s updated the group. %1$s called you · %2$s Called %s @@ -1431,7 +1432,8 @@ Draft: You called Called you - Missed call + Missed audio call + Missed video call Media message Sticker View-once photo