diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt index 44c95f83b2..ac8718f904 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt @@ -153,13 +153,12 @@ class CallLogAdapter( val event = model.call.call.event val direction = model.call.call.direction - val type = model.call.call.type binding.callRecipientAvatar.setAvatar(GlideApp.with(binding.callRecipientAvatar), model.call.peer, true) binding.callRecipientBadge.setBadgeFromRecipient(model.call.peer) binding.callRecipientName.text = model.call.peer.getDisplayName(context) presentCallInfo(event, direction, model.call.date) - presentCallType(type, model.call.peer) + presentCallType(model) } private fun presentCallInfo(event: CallTable.Event, direction: CallTable.Direction, date: Long) { @@ -190,24 +189,47 @@ class CallLogAdapter( binding.callInfo.setTextColor(color) } - private fun presentCallType(callType: CallTable.Type, peer: Recipient) { - when (callType) { + private fun presentCallType(model: CallModel) { + when (model.call.call.type) { CallTable.Type.AUDIO_CALL -> { binding.callType.setImageResource(R.drawable.symbol_phone_24) - binding.callType.setOnClickListener { onStartAudioCallClicked(peer) } + binding.callType.setOnClickListener { onStartAudioCallClicked(model.call.peer) } + binding.callType.visible = true + binding.groupCallButton.visible = false } CallTable.Type.VIDEO_CALL -> { binding.callType.setImageResource(R.drawable.symbol_video_24) - binding.callType.setOnClickListener { onStartVideoCallClicked(peer) } + binding.callType.setOnClickListener { onStartVideoCallClicked(model.call.peer) } + binding.callType.visible = true + binding.groupCallButton.visible = false } CallTable.Type.GROUP_CALL, CallTable.Type.AD_HOC_CALL -> { - // TODO [alex] -- Group call button + binding.callType.setImageResource(R.drawable.symbol_video_24) + binding.callType.setOnClickListener { onStartVideoCallClicked(model.call.peer) } + binding.groupCallButton.setOnClickListener { onStartVideoCallClicked(model.call.peer) } + + when (model.call.groupCallState) { + CallLogRow.GroupCallState.NONE, CallLogRow.GroupCallState.FULL -> { + binding.callType.visible = true + binding.groupCallButton.visible = false + } + CallLogRow.GroupCallState.ACTIVE, CallLogRow.GroupCallState.LOCAL_USER_JOINED -> { + binding.callType.visible = false + binding.groupCallButton.visible = true + + binding.groupCallButton.setText( + if (model.call.groupCallState == CallLogRow.GroupCallState.LOCAL_USER_JOINED) { + R.string.CallLogAdapter__return + } else { + R.string.CallLogAdapter__join + } + ) + } + } } } - - binding.callType.visible = true } @DrawableRes diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt index 196cadcd68..4e5453ac46 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt @@ -26,16 +26,11 @@ class CallLogRepository : CallLogPagedDataSource.CallRepository { refresh() } - val messageObserver = DatabaseObserver.MessageObserver { - refresh() - } - ApplicationDependencies.getDatabaseObserver().registerConversationListObserver(databaseObserver) - ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(messageObserver) + ApplicationDependencies.getDatabaseObserver().registerCallUpdateObserver(databaseObserver) emitter.setCancellable { ApplicationDependencies.getDatabaseObserver().unregisterObserver(databaseObserver) - ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageObserver) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt index ad540bb2dd..bfb0f3ba61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRow.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.calls.log import org.thoughtcrime.securesms.database.CallTable +import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails import org.thoughtcrime.securesms.recipients.Recipient /** @@ -17,6 +18,7 @@ sealed class CallLogRow { val call: CallTable.Call, val peer: Recipient, val date: Long, + val groupCallState: GroupCallState, override val id: Id = Id.Call(call.callId) ) : CallLogRow() @@ -36,4 +38,48 @@ sealed class CallLogRow { object ClearFilter : Id() object CreateCallLink : Id() } + + enum class GroupCallState { + /** + * No group call available. + */ + NONE, + + /** + * Active, but the local user is not in the call. + */ + ACTIVE, + + /** + * Active and the local user is in the call + */ + LOCAL_USER_JOINED, + + /** + * Active but the call is full. + */ + FULL; + + companion object { + fun fromDetails(groupCallUpdateDetails: GroupCallUpdateDetails?): GroupCallState { + if (groupCallUpdateDetails == null) { + return NONE + } + + if (groupCallUpdateDetails.isCallFull) { + return FULL + } + + if (groupCallUpdateDetails.inCallUuidsList.contains(Recipient.self().requireServiceId().uuid().toString())) { + return LOCAL_USER_JOINED + } + + return if (groupCallUpdateDetails.inCallUuidsCount > 0) { + ACTIVE + } else { + NONE + } + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt index d1014739b0..27f9e159a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt @@ -15,6 +15,7 @@ import org.signal.core.util.readToSingleLong import org.signal.core.util.readToSingleObject import org.signal.core.util.requireLong import org.signal.core.util.requireObject +import org.signal.core.util.requireString import org.signal.core.util.select import org.signal.core.util.update import org.signal.core.util.withinTransaction @@ -22,6 +23,7 @@ import org.signal.ringrtc.CallId import org.signal.ringrtc.CallManager.RingUpdate import org.thoughtcrime.securesms.calls.log.CallLogFilter import org.thoughtcrime.securesms.calls.log.CallLogRow +import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.CallSyncEventJob @@ -93,6 +95,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl } ApplicationDependencies.getMessageNotifier().updateNotification(context) + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() Log.i(TAG, "Inserted call: $callId type: $type direction: $direction event:$event") } @@ -117,6 +120,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl SignalDatabase.messages.updateCallLog(call.messageId!!, call.messageType) ApplicationDependencies.getMessageNotifier().updateNotification(context) + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() } call @@ -205,6 +209,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl CallSyncEventJob.enqueueDeleteSyncEvents(toSync) ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary() + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() } // region Group / Ad-Hoc Calling @@ -228,6 +233,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl } ApplicationDependencies.getMessageNotifier().updateNotification(context) + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() Log.d(TAG, "Marked group call event for deletion: ${call.callId}") } @@ -275,6 +281,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl .run() ApplicationDependencies.getMessageNotifier().updateNotification(context) + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() Log.d(TAG, "Transitioned group call ${call.callId} from ${call.event} to $newEvent") } @@ -316,6 +323,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl ) .run() } + + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() } fun insertOrUpdateGroupCallFromExternalEvent( @@ -434,6 +443,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl } else { Log.d(TAG, "Skipping call event processing for null era id.") } + + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() } /** @@ -445,7 +456,9 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl peekJoinedUuids: Collection, isCallFull: Boolean ): Boolean { - return SignalDatabase.messages.updatePreviousGroupCall(threadId, peekGroupCallEraId, peekJoinedUuids, isCallFull) + val sameEraId = SignalDatabase.messages.updatePreviousGroupCall(threadId, peekGroupCallEraId, peekJoinedUuids, isCallFull) + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() + return sameEraId } fun insertOrUpdateGroupCallFromRingState( @@ -545,6 +558,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl createEventFromRingState(ringId, groupRecipientId, ringerRecipient, event, dateReceived) } + + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() } private fun updateEventFromRingState( @@ -634,6 +649,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl SignalDatabase.messages.updateCallTimestamps(call.messageId, timestamp) } } + + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() } private fun setMessageId(callId: Long, messageId: MessageId) { @@ -723,7 +740,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl //language=sql val statement = """ SELECT - ${if (isCount) "COUNT(*)," else "$TABLE_NAME.*, ${MessageTable.DATE_RECEIVED},"} + ${if (isCount) "COUNT(*)," else "$TABLE_NAME.*, ${MessageTable.DATE_RECEIVED}, ${MessageTable.BODY},"} LOWER( COALESCE( NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.SYSTEM_JOINED_NAME}, ''), @@ -756,10 +773,13 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl val call = Call.deserialize(it) val recipient = Recipient.resolved(call.peer) val date = it.requireLong(MessageTable.DATE_RECEIVED) + val groupCallDetails = GroupCallUpdateDetailsUtil.parse(it.requireString(MessageTable.BODY)) + CallLogRow.Call( call = call, peer = recipient, - date = date + date = date, + groupCallState = CallLogRow.GroupCallState.fromDetails(groupCallDetails) ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java index be666ad982..ac8344b1ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseObserver.java @@ -45,6 +45,8 @@ public class DatabaseObserver { private static final String KEY_SCHEDULED_MESSAGES = "ScheduledMessages"; private static final String KEY_CONVERSATION_DELETES = "ConversationDeletes"; + private static final String KEY_CALL_UPDATES = "CallUpdates"; + private final Application application; private final Executor executor; @@ -64,6 +66,8 @@ public class DatabaseObserver { private final Set notificationProfileObservers; private final Map> storyObservers; + private final Set callUpdateObservers; + public DatabaseObserver(Application application) { this.application = application; this.executor = new SerialExecutor(SignalExecutors.BOUNDED); @@ -82,6 +86,7 @@ public class DatabaseObserver { this.notificationProfileObservers = new HashSet<>(); this.storyObservers = new HashMap<>(); this.scheduledMessageObservers = new HashMap<>(); + this.callUpdateObservers = new HashSet<>(); } public void registerConversationListObserver(@NonNull Observer listener) { @@ -177,6 +182,10 @@ public class DatabaseObserver { }); } + public void registerCallUpdateObserver(@NonNull Observer observer) { + executor.execute(() -> callUpdateObservers.add(observer)); + } + public void unregisterObserver(@NonNull Observer listener) { executor.execute(() -> { conversationListObservers.remove(listener); @@ -191,6 +200,7 @@ public class DatabaseObserver { unregisterMapped(storyObservers, listener); unregisterMapped(scheduledMessageObservers, listener); unregisterMapped(conversationDeleteObservers, listener); + callUpdateObservers.remove(listener); }); } @@ -328,6 +338,10 @@ public class DatabaseObserver { }); } + public void notifyCallUpdateObservers() { + runPostSuccessfulTransaction(KEY_CALL_UPDATES, () -> notifySet(callUpdateObservers)); + } + private void runPostSuccessfulTransaction(@NonNull String dedupeKey, @NonNull Runnable runnable) { SignalDatabase.runPostSuccessfulTransaction(dedupeKey, () -> { executor.execute(runnable); diff --git a/app/src/main/res/layout/call_log_adapter_item.xml b/app/src/main/res/layout/call_log_adapter_item.xml index 6fb9e2aabe..2568fb510c 100644 --- a/app/src/main/res/layout/call_log_adapter_item.xml +++ b/app/src/main/res/layout/call_log_adapter_item.xml @@ -126,4 +126,23 @@ app:tint="@color/signal_colorOnSurface" tools:visibility="visible" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e36fb5d54..229103b712 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5775,6 +5775,10 @@ Outgoing Missed + + Join + + Return