From 61373b8b51e14c5864e63bb1b27743fa47fd20ff Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 12 Feb 2021 19:13:45 +0100 Subject: [PATCH 01/10] Timeline: start refactoring the Interceptor mechanism --- .../app/core/epoxy/TimelineEmptyItem.kt | 34 ++++ .../room/detail/ScrollOnNewMessageCallback.kt | 3 +- .../timeline/TimelineEventController.kt | 92 ++--------- .../timeline/factory/TimelineItemFactory.kt | 12 +- .../TimelineControllerInterceptorHelper.kt | 154 ++++++++++++++++++ .../detail/timeline/item/BaseEventItem.kt | 8 +- .../room/detail/timeline/item/IsEventItem.kt} | 16 +- ...item_empty.xml => item_timeline_empty.xml} | 0 8 files changed, 226 insertions(+), 93 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt rename vector/src/main/java/im/vector/app/{core/epoxy/EmptyItem.kt => features/home/room/detail/timeline/item/IsEventItem.kt} (65%) rename vector/src/main/res/layout/{item_empty.xml => item_timeline_empty.xml} (100%) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt new file mode 100644 index 00000000000..2339287cbe2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.epoxy + +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.features.home.room.detail.timeline.item.IsEventItem + +@EpoxyModelClass(layout = R.layout.item_timeline_empty) +abstract class TimelineEmptyItem : VectorEpoxyModel(), IsEventItem { + + @EpoxyAttribute lateinit var eventId: String + + override fun getEventIds(): List { + return listOf(eventId) + } + + class Holder : VectorEpoxyHolder() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt index af56e2eb02e..dbe4c484ca2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem +import im.vector.app.features.home.room.detail.timeline.item.IsEventItem import timber.log.Timber import java.util.concurrent.CopyOnWriteArrayList @@ -47,7 +48,7 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager, if (layoutManager.findFirstVisibleItemPosition() != position) { return } - val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? BaseEventItem ?: return + val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? IsEventItem ?: return val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds) if (indexOfFirstNewItem != -1) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 29871cf307c..9acd34c8275 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -38,18 +38,15 @@ import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItem import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder -import im.vector.app.features.home.room.detail.timeline.helper.ReadMarkerVisibilityStateChangedListener +import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider -import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem -import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData -import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer @@ -194,75 +191,20 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } + private val interceptorHelper = TimelineControllerInterceptorHelper( + ::positionOfReadMarker, + adapterPositionMapping, + vectorPreferences, + callManager + ) + init { addInterceptor(this) requestModelBuild() } - // Update position when we are building new items override fun intercept(models: MutableList>) = synchronized(modelCache) { - positionOfReadMarker = null - adapterPositionMapping.clear() - val callIds = mutableSetOf() - val modelsIterator = models.listIterator() - val showHiddenEvents = vectorPreferences.shouldShowHiddenEvents() - modelsIterator.withIndex().forEach { - val index = it.index - val epoxyModel = it.value - if (epoxyModel is CallTileTimelineItem) { - val callId = epoxyModel.attributes.callId - // We should remove the call tile if we already have one for this call or - // if this is an active call tile without an actual call (which can happen with permalink) - val shouldRemoveCallItem = callIds.contains(callId) - || (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive()) - if (shouldRemoveCallItem && !showHiddenEvents) { - modelsIterator.remove() - return@forEach - } - callIds.add(callId) - } - if (epoxyModel is BaseEventItem) { - epoxyModel.getEventIds().forEach { eventId -> - adapterPositionMapping[eventId] = index - } - } - } - val currentUnreadState = this.unreadState - if (currentUnreadState is UnreadState.HasUnread) { - val position = adapterPositionMapping[currentUnreadState.firstUnreadEventId]?.plus(1) - positionOfReadMarker = position - if (position != null) { - val readMarker = TimelineReadMarkerItem_() - .also { - it.id("read_marker") - it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback)) - } - models.add(position, readMarker) - } - } - val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false - if (shouldAddBackwardPrefetch) { - val indexOfPrefetchBackward = (previousModelsSize - 1) - .coerceAtMost(models.size - DEFAULT_PREFETCH_THRESHOLD) - .coerceAtLeast(0) - - val loadingItem = LoadingItem_() - .id("prefetch_backward_loading${System.currentTimeMillis()}") - .showLoader(false) - .setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS) - - models.add(indexOfPrefetchBackward, loadingItem) - } - val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false - if (shouldAddForwardPrefetch) { - val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(models.size - 1) - val loadingItem = LoadingItem_() - .id("prefetch_forward_loading${System.currentTimeMillis()}") - .showLoader(false) - .setVisibilityStateChangedListener(Timeline.Direction.FORWARDS) - models.add(indexOfPrefetchForward, loadingItem) - } - previousModelsSize = models.size + interceptorHelper.intercept(models, unreadState, timeline, callback) } fun update(viewState: RoomDetailViewState) { @@ -431,6 +373,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } + private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ { + return onVisibilityStateChanged { _, _, visibilityState -> + if (visibilityState == VisibilityState.VISIBLE) { + callback?.onLoadMore(direction) + } + } + } + private fun updateUTDStates(event: TimelineEvent, nextEvent: TimelineEvent?) { if (vectorPreferences.labShowCompleteHistoryInEncryptedRoom()) { return @@ -461,14 +411,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec return shouldAdd } - private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ { - return onVisibilityStateChanged { _, _, visibilityState -> - if (visibilityState == VisibilityState.VISIBLE) { - callback?.onLoadMore(direction) - } - } - } - fun searchPositionOfEvent(eventId: String?): Int? = synchronized(modelCache) { return adapterPositionMapping[eventId] } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 982ceb906cd..837d35d15f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -16,7 +16,8 @@ package im.vector.app.features.home.room.detail.timeline.factory -import im.vector.app.core.epoxy.EmptyItem_ +import im.vector.app.core.epoxy.TimelineEmptyItem +import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController @@ -114,6 +115,13 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me Timber.e(throwable, "failed to create message item") defaultItemFactory.create(event, highlight, callback, throwable) } - return (computedModel ?: EmptyItem_()) + return (computedModel ?: buildEmptyItem(event)) } + + private fun buildEmptyItem(timelineEvent: TimelineEvent): TimelineEmptyItem{ + return TimelineEmptyItem_() + .id(timelineEvent.localId) + .eventId(timelineEvent.eventId) + } + } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt new file mode 100644 index 00000000000..30b11b5e2cb --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.helper + +import com.airbnb.epoxy.EpoxyModel +import im.vector.app.features.call.webrtc.WebRtcCallManager +import im.vector.app.features.home.room.detail.UnreadState +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem +import com.airbnb.epoxy.VisibilityState +import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem +import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import im.vector.app.core.epoxy.LoadingItem_ +import im.vector.app.core.epoxy.TimelineEmptyItem_ +import im.vector.app.features.home.room.detail.timeline.item.IsEventItem +import timber.log.Timber +import kotlin.reflect.KMutableProperty0 + + +private const val DEFAULT_PREFETCH_THRESHOLD = 30 + +class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMutableProperty0, + private val adapterPositionMapping: MutableMap, + private val vectorPreferences: VectorPreferences, + private val callManager: WebRtcCallManager +) { + + private var previousModelsSize = 0 + + // Update position when we are building new items + fun intercept( + models: MutableList>, + unreadState: UnreadState, + timeline: Timeline?, + callback: TimelineEventController.Callback? + ) { + positionOfReadMarker.set(null) + adapterPositionMapping.clear() + val callIds = mutableSetOf() + + // Add some prefetch loader if needed + models.addBackwardPrefetchIfNeeded(timeline, callback) + models.addForwardPrefetchIfNeeded(timeline, callback) + + val modelsIterator = models.listIterator() + val showHiddenEvents = vectorPreferences.shouldShowHiddenEvents() + var index = 0 + val firstUnreadEventId = (unreadState as? UnreadState.HasUnread)?.firstUnreadEventId + // Then iterate on models so we have the exact positions in the adapter + modelsIterator.forEach { epoxyModel -> + Timber.v("Index of model:${epoxyModel::class}: $index") + if (epoxyModel is IsEventItem) { + epoxyModel.getEventIds().forEach { eventId -> + adapterPositionMapping[eventId] = index + if (eventId == firstUnreadEventId) { + modelsIterator.addReadMarkerItem(callback) + index++ + positionOfReadMarker.set(index) + } + } + } + if (epoxyModel is CallTileTimelineItem) { + modelsIterator.removeCallItemIfNeeded(epoxyModel, callIds, showHiddenEvents) + } + index++ + } + previousModelsSize = models.size + } + + private fun MutableListIterator>.addReadMarkerItem(callback: TimelineEventController.Callback?) { + val readMarker = TimelineReadMarkerItem_() + .also { + it.id("read_marker") + it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback)) + } + add(readMarker) + } + + private fun MutableListIterator>.removeCallItemIfNeeded( + epoxyModel: CallTileTimelineItem, + callIds: MutableSet, + showHiddenEvents: Boolean + ) { + val callId = epoxyModel.attributes.callId + // We should remove the call tile if we already have one for this call or + // if this is an active call tile without an actual call (which can happen with permalink) + val shouldRemoveCallItem = callIds.contains(callId) + || (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive()) + if (shouldRemoveCallItem && !showHiddenEvents) { + remove() + val emptyItem = TimelineEmptyItem_() + .id(epoxyModel.id()) + .eventId(epoxyModel.attributes.informationData.eventId) + add(emptyItem) + } + callIds.add(callId) + } + + private fun MutableList>.addBackwardPrefetchIfNeeded(timeline: Timeline?, callback: TimelineEventController.Callback?) { + val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false + if (shouldAddBackwardPrefetch) { + val indexOfPrefetchBackward = (previousModelsSize - 1) + .coerceAtMost(size - DEFAULT_PREFETCH_THRESHOLD) + .coerceAtLeast(0) + + val loadingItem = LoadingItem_() + .id("prefetch_backward_loading${System.currentTimeMillis()}") + .showLoader(false) + .setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS, callback) + + add(indexOfPrefetchBackward, loadingItem) + } + } + + private fun MutableList>.addForwardPrefetchIfNeeded(timeline: Timeline?,callback: TimelineEventController.Callback?) { + val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false + if (shouldAddForwardPrefetch) { + val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(size - 1) + val loadingItem = LoadingItem_() + .id("prefetch_forward_loading${System.currentTimeMillis()}") + .showLoader(false) + .setVisibilityStateChangedListener(Timeline.Direction.FORWARDS, callback) + add(indexOfPrefetchForward, loadingItem) + } + } + + private fun LoadingItem_.setVisibilityStateChangedListener( + direction: Timeline.Direction, + callback: TimelineEventController.Callback? + ): LoadingItem_ { + return onVisibilityStateChanged { _, _, visibilityState -> + if (visibilityState == VisibilityState.VISIBLE) { + callback?.onLoadMore(direction) + } + } + } + +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index e6174899022..8546ed7e0f2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -32,7 +32,7 @@ import im.vector.app.core.utils.DimensionConverter /** * Children must override getViewType() */ -abstract class BaseEventItem : VectorEpoxyModel() { +abstract class BaseEventItem : VectorEpoxyModel(), IsEventItem { // To use for instance when opening a permalink with an eventId @EpoxyAttribute @@ -53,12 +53,6 @@ abstract class BaseEventItem : VectorEpoxyModel holder.checkableBackground.isChecked = highlighted } - /** - * Returns the eventIds associated with the EventItem. - * Will generally get only one, but it handles the merging items. - */ - abstract fun getEventIds(): List - abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() { val leftGuideline by bind(R.id.messageStartGuideline) val checkableBackground by bind(R.id.messageSelectedBackground) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/EmptyItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt similarity index 65% rename from vector/src/main/java/im/vector/app/core/epoxy/EmptyItem.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt index aaf870667b9..059bdbea43f 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/EmptyItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2021 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * limitations under the License. */ -package im.vector.app.core.epoxy +package im.vector.app.features.home.room.detail.timeline.item -import com.airbnb.epoxy.EpoxyModelClass -import im.vector.app.R - -@EpoxyModelClass(layout = R.layout.item_empty) -abstract class EmptyItem : VectorEpoxyModel() { - class Holder : VectorEpoxyHolder() +interface IsEventItem { + /** + * Returns the eventIds associated with the EventItem. + * Will generally get only one, but it handles the merging items. + */ + fun getEventIds(): List } diff --git a/vector/src/main/res/layout/item_empty.xml b/vector/src/main/res/layout/item_timeline_empty.xml similarity index 100% rename from vector/src/main/res/layout/item_empty.xml rename to vector/src/main/res/layout/item_timeline_empty.xml From 109a9e816bc5e510d22cff9b0bf62fc772aab60a Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 15 Feb 2021 12:03:50 +0100 Subject: [PATCH 02/10] Timeline: fix crash on mutable iterator --- .../timeline/helper/TimelineControllerInterceptorHelper.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 30b11b5e2cb..591cd1b4175 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -90,6 +90,8 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback)) } add(readMarker) + // Use next as we still have some process to do before the next iterator loop + next() } private fun MutableListIterator>.removeCallItemIfNeeded( From 96b02d31540a10f343d3a19ea3e2d760566fc94b Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 15 Feb 2021 14:49:45 +0100 Subject: [PATCH 03/10] VoIP: PSTN support was done too early --- .../app/features/call/webrtc/PSTNProtocol.kt | 43 --------- .../call/webrtc/PSTNProtocolChecker.kt | 88 +++++++++++++++++++ .../features/call/webrtc/WebRtcCallManager.kt | 35 ++++---- .../home/room/detail/RoomDetailViewModel.kt | 1 + 4 files changed, 105 insertions(+), 62 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocol.kt create mode 100644 vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocol.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocol.kt deleted file mode 100644 index 3e6d2df6904..00000000000 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocol.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.call.webrtc - -import kotlinx.coroutines.delay -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol - -private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn" -private const val PSTN_MATRIX_KEY = "m.protocol.pstn" - -suspend fun Session.getSupportedPSTN(maxTries: Int): String? { - val thirdPartyProtocols: Map = try { - thirdPartyService().getThirdPartyProtocols() - } catch (failure: Throwable) { - if (maxTries == 1) { - return null - } else { - // Wait for 10s before trying again - delay(10_000L) - return getSupportedPSTN(maxTries - 1) - } - } - return when { - thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY - thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY - else -> null - } -} diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt new file mode 100644 index 00000000000..6452bc3964c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.call.webrtc + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol +import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject +import javax.inject.Singleton + +private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn" +private const val PSTN_MATRIX_KEY = "m.protocol.pstn" + +@Singleton +class PSTNProtocolChecker @Inject constructor() { + + private var alreadyChecked = AtomicBoolean(false) + + private val pstnSupportListeners = emptyList().toMutableList() + fun addPstnSupportListener(listener: WebRtcCallManager.PSTNSupportListener) { + pstnSupportListeners.add(listener) + } + + fun removePstnSupportListener(listener: WebRtcCallManager.PSTNSupportListener) { + pstnSupportListeners.remove(listener) + } + + var supportedPSTNProtocol: String? = null + private set + + fun checkForPSTNSupportIfNeeded(currentSession: Session?) { + if (alreadyChecked.get()) return + GlobalScope.checkForPSTNSupport(currentSession) + } + + private fun CoroutineScope.checkForPSTNSupport(currentSession: Session?) = launch { + try { + supportedPSTNProtocol = currentSession?.getSupportedPSTN(3) + alreadyChecked.set(true) + if (supportedPSTNProtocol != null) { + pstnSupportListeners.forEach { + tryOrNull { it.onPSTNSupportUpdated() } + } + } + } catch (failure: Throwable) { + Timber.v("Fail to get supported PSTN, will check again next time.") + } + } +} + +suspend fun Session.getSupportedPSTN(maxTries: Int): String? { + val thirdPartyProtocols: Map = try { + thirdPartyService().getThirdPartyProtocols() + } catch (failure: Throwable) { + if (maxTries == 1) { + throw failure + } else { + // Wait for 10s before trying again + delay(10_000L) + return getSupportedPSTN(maxTries - 1) + } + } + return when { + thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY + thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY + else -> null + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index 95728e0a97c..7f68a4bf5c2 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -59,7 +59,8 @@ import javax.inject.Singleton @Singleton class WebRtcCallManager @Inject constructor( private val context: Context, - private val activeSessionDataSource: ActiveSessionDataSource + private val activeSessionDataSource: ActiveSessionDataSource, + private val pstnProtocolChecker: PSTNProtocolChecker ) : CallListener, LifecycleObserver { private val currentSession: Session? @@ -74,13 +75,19 @@ class WebRtcCallManager @Inject constructor( fun onPSTNSupportUpdated() } - private val pstnSupportListeners = emptyList().toMutableList() + val supportedPSTNProtocol: String? + get() = pstnProtocolChecker.supportedPSTNProtocol + + val supportsPSTNProtocol: Boolean + get() = supportedPSTNProtocol != null + + fun addPstnSupportListener(listener: PSTNSupportListener) { - pstnSupportListeners.add(listener) + pstnProtocolChecker.addPstnSupportListener(listener) } fun removePstnSupportListener(listener: PSTNSupportListener) { - pstnSupportListeners.remove(listener) + pstnProtocolChecker.removePstnSupportListener(listener) } private val currentCallsListeners = CopyOnWriteArrayList() @@ -104,30 +111,16 @@ class WebRtcCallManager @Inject constructor( private var peerConnectionFactory: PeerConnectionFactory? = null private val executor = Executors.newSingleThreadExecutor() private val dispatcher = executor.asCoroutineDispatcher() - var supportedPSTNProtocol: String? = null - private set - val supportsPSTNProtocol: Boolean - get() = supportedPSTNProtocol != null private val rootEglBase by lazy { EglUtils.rootEglBase } private var isInBackground: Boolean = true - init { - GlobalScope.launch { - supportedPSTNProtocol = currentSession?.getSupportedPSTN(3) - if (supportedPSTNProtocol != null) { - pstnSupportListeners.forEach { - tryOrNull { it.onPSTNSupportUpdated() } - } - } - } - } - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { isInBackground = false + checkForPSTNSupportIfNeeded() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @@ -167,6 +160,10 @@ class WebRtcCallManager @Inject constructor( return callsByCallId.values.toList() } + fun checkForPSTNSupportIfNeeded() { + pstnProtocolChecker.checkForPSTNSupportIfNeeded(currentSession) + } + /** * @return a set of all advertised call during the lifetime of the app. */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index cb93db1d722..acc295b2427 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -177,6 +177,7 @@ class RoomDetailViewModel @AssistedInject constructor( // Inform the SDK that the room is displayed session.onRoomDisplayed(initialState.roomId) callManager.addPstnSupportListener(this) + callManager.checkForPSTNSupportIfNeeded() chatEffectManager.delegate = this } From 5e3e5d26489165efc3502ad588c3dd4a33fa2c0e Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 15 Feb 2021 15:35:11 +0100 Subject: [PATCH 04/10] Clean code --- .../im/vector/app/features/call/webrtc/WebRtcCallManager.kt | 4 ---- .../features/home/room/detail/ScrollOnNewMessageCallback.kt | 1 - .../home/room/detail/timeline/factory/TimelineItemFactory.kt | 3 +-- .../timeline/helper/TimelineControllerInterceptorHelper.kt | 5 +---- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index 7f68a4bf5c2..a2b82de0bb2 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -26,9 +26,7 @@ import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.utils.EglUtils import im.vector.app.push.fcm.FcmHelper -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.call.CallListener @@ -81,7 +79,6 @@ class WebRtcCallManager @Inject constructor( val supportsPSTNProtocol: Boolean get() = supportedPSTNProtocol != null - fun addPstnSupportListener(listener: PSTNSupportListener) { pstnProtocolChecker.addPstnSupportListener(listener) } @@ -112,7 +109,6 @@ class WebRtcCallManager @Inject constructor( private val executor = Executors.newSingleThreadExecutor() private val dispatcher = executor.asCoroutineDispatcher() - private val rootEglBase by lazy { EglUtils.rootEglBase } private var isInBackground: Boolean = true diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt index dbe4c484ca2..e7543278ab6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail import androidx.recyclerview.widget.LinearLayoutManager import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem import im.vector.app.features.home.room.detail.timeline.item.IsEventItem import timber.log.Timber import java.util.concurrent.CopyOnWriteArrayList diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 837d35d15f5..18ffa995f89 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -118,10 +118,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me return (computedModel ?: buildEmptyItem(event)) } - private fun buildEmptyItem(timelineEvent: TimelineEvent): TimelineEmptyItem{ + private fun buildEmptyItem(timelineEvent: TimelineEvent): TimelineEmptyItem { return TimelineEmptyItem_() .id(timelineEvent.localId) .eventId(timelineEvent.eventId) } - } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 591cd1b4175..5eddedae2fb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -20,7 +20,6 @@ import com.airbnb.epoxy.EpoxyModel import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.detail.UnreadState import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem import com.airbnb.epoxy.VisibilityState import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem import im.vector.app.features.settings.VectorPreferences @@ -32,7 +31,6 @@ import im.vector.app.features.home.room.detail.timeline.item.IsEventItem import timber.log.Timber import kotlin.reflect.KMutableProperty0 - private const val DEFAULT_PREFETCH_THRESHOLD = 30 class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMutableProperty0, @@ -130,7 +128,7 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut } } - private fun MutableList>.addForwardPrefetchIfNeeded(timeline: Timeline?,callback: TimelineEventController.Callback?) { + private fun MutableList>.addForwardPrefetchIfNeeded(timeline: Timeline?, callback: TimelineEventController.Callback?) { val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false if (shouldAddForwardPrefetch) { val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(size - 1) @@ -152,5 +150,4 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut } } } - } From 3170d4428cb60bab7befda6a0b9482c18316e91d Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 15 Feb 2021 19:34:37 +0100 Subject: [PATCH 05/10] VoIP: extract the PSTNProtocoltChecker to the SDK --- .../api/session/call/CallSignalingService.kt | 7 +- .../api/session/call/PSTNProtocolChecker.kt | 98 +++++++++++++ .../call/DefaultCallSignalingService.kt | 20 ++- .../app/features/call/VectorCallViewModel.kt | 38 ++--- .../call/webrtc/PSTNProtocolChecker.kt | 88 ----------- .../app/features/call/webrtc/WebRtcCall.kt | 5 +- .../features/call/webrtc/WebRtcCallManager.kt | 25 ++-- .../home/room/detail/RoomDetailViewModel.kt | 137 +++++++++--------- 8 files changed, 207 insertions(+), 211 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt delete mode 100644 vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt index c6bdcd19c77..dc67aa536af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt @@ -16,12 +16,11 @@ package org.matrix.android.sdk.api.session.call -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - interface CallSignalingService { - fun getTurnServer(callback: MatrixCallback): Cancelable + suspend fun getTurnServer(): TurnServerResponse + + fun getPSTNProtocolChecker(): PSTNProtocolChecker /** * Create an outgoing call diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt new file mode 100644 index 00000000000..41486fad5a1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.call + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.thirdparty.GetThirdPartyProtocolsTask +import org.matrix.android.sdk.internal.task.TaskExecutor +import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject + +private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn" +private const val PSTN_MATRIX_KEY = "m.protocol.pstn" + +/** + * This class is responsible for checking if the HS support the PSTN protocol. + * As long as the request succeed, it'll check only once by session. + */ +@SessionScope +class PSTNProtocolChecker @Inject internal constructor(private val taskExecutor: TaskExecutor, + private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask) { + + interface Listener { + fun onPSTNSupportUpdated() + } + + private var alreadyChecked = AtomicBoolean(false) + + private val pstnSupportListeners = emptyList().toMutableList() + + fun addListener(listener: Listener) { + pstnSupportListeners.add(listener) + } + + fun removeListener(listener: Listener) { + pstnSupportListeners.remove(listener) + } + + var supportedPSTNProtocol: String? = null + private set + + fun checkForPSTNSupportIfNeeded() { + if (alreadyChecked.get()) return + taskExecutor.executorScope.checkForPSTNSupport() + } + + private fun CoroutineScope.checkForPSTNSupport() = launch { + try { + supportedPSTNProtocol = getSupportedPSTN(3) + alreadyChecked.set(true) + if (supportedPSTNProtocol != null) { + pstnSupportListeners.forEach { + tryOrNull { it.onPSTNSupportUpdated() } + } + } + } catch (failure: Throwable) { + Timber.v("Fail to get supported PSTN, will check again next time.") + } + } + + private suspend fun getSupportedPSTN(maxTries: Int): String? { + val thirdPartyProtocols: Map = try { + getThirdPartyProtocolsTask.execute(Unit) + } catch (failure: Throwable) { + if (maxTries == 1) { + throw failure + } else { + // Wait for 10s before trying again + delay(10_000L) + return getSupportedPSTN(maxTries - 1) + } + } + return when { + thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY + thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY + else -> null + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt index 10690c59dee..7d046cb6422 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt @@ -16,16 +16,12 @@ package org.matrix.android.sdk.internal.session.call -import kotlinx.coroutines.Dispatchers -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.call.MxCall +import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker import org.matrix.android.sdk.api.session.call.TurnServerResponse -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.launchToCallback import timber.log.Timber import javax.inject.Inject @@ -34,14 +30,16 @@ internal class DefaultCallSignalingService @Inject constructor( private val callSignalingHandler: CallSignalingHandler, private val mxCallFactory: MxCallFactory, private val activeCallHandler: ActiveCallHandler, - private val taskExecutor: TaskExecutor, - private val turnServerDataSource: TurnServerDataSource + private val turnServerDataSource: TurnServerDataSource, + private val pstnProtocolChecker: PSTNProtocolChecker ) : CallSignalingService { - override fun getTurnServer(callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) { - turnServerDataSource.getTurnServer() - } + override suspend fun getTurnServer(): TurnServerResponse { + return turnServerDataSource.getTurnServer() + } + + override fun getPSTNProtocolChecker(): PSTNProtocolChecker { + return pstnProtocolChecker } override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index 25b2a80a854..8a2d56a5a25 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.call +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory @@ -29,17 +30,16 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCallManager -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxPeerConnectionState -import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import java.util.Timer -import java.util.TimerTask class VectorCallViewModel @AssistedInject constructor( @Assisted initialState: VectorCallViewState, @@ -50,7 +50,7 @@ class VectorCallViewModel @AssistedInject constructor( private var call: WebRtcCall? = null - private var connectionTimeoutTimer: Timer? = null + private var connectionTimeoutJob: Job? = null private var hasBeenConnectedOnce = false private val callListener = object : WebRtcCall.Listener { @@ -92,26 +92,20 @@ class VectorCallViewModel @AssistedInject constructor( val callState = call.state if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { hasBeenConnectedOnce = true - connectionTimeoutTimer?.cancel() - connectionTimeoutTimer = null + connectionTimeoutJob?.cancel() + connectionTimeoutJob = null } else { // do we reset as long as it's moving? - connectionTimeoutTimer?.cancel() + connectionTimeoutJob?.cancel() if (hasBeenConnectedOnce) { - connectionTimeoutTimer = Timer().apply { - schedule(object : TimerTask() { - override fun run() { - session.callSignalingService().getTurnServer(object : MatrixCallback { - override fun onFailure(failure: Throwable) { - _viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null)) - } - - override fun onSuccess(data: TurnServerResponse) { - _viewEvents.post(VectorCallViewEvents.ConnectionTimeout(data)) - } - }) - } - }, 30_000) + connectionTimeoutJob = viewModelScope.launch { + delay(30_000) + try { + val turn = session.callSignalingService().getTurnServer() + _viewEvents.post(VectorCallViewEvents.ConnectionTimeout(turn)) + } catch (failure: Throwable) { + _viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null)) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt deleted file mode 100644 index 6452bc3964c..00000000000 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/PSTNProtocolChecker.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.call.webrtc - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol -import timber.log.Timber -import java.util.concurrent.atomic.AtomicBoolean -import javax.inject.Inject -import javax.inject.Singleton - -private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn" -private const val PSTN_MATRIX_KEY = "m.protocol.pstn" - -@Singleton -class PSTNProtocolChecker @Inject constructor() { - - private var alreadyChecked = AtomicBoolean(false) - - private val pstnSupportListeners = emptyList().toMutableList() - fun addPstnSupportListener(listener: WebRtcCallManager.PSTNSupportListener) { - pstnSupportListeners.add(listener) - } - - fun removePstnSupportListener(listener: WebRtcCallManager.PSTNSupportListener) { - pstnSupportListeners.remove(listener) - } - - var supportedPSTNProtocol: String? = null - private set - - fun checkForPSTNSupportIfNeeded(currentSession: Session?) { - if (alreadyChecked.get()) return - GlobalScope.checkForPSTNSupport(currentSession) - } - - private fun CoroutineScope.checkForPSTNSupport(currentSession: Session?) = launch { - try { - supportedPSTNProtocol = currentSession?.getSupportedPSTN(3) - alreadyChecked.set(true) - if (supportedPSTNProtocol != null) { - pstnSupportListeners.forEach { - tryOrNull { it.onPSTNSupportUpdated() } - } - } - } catch (failure: Throwable) { - Timber.v("Fail to get supported PSTN, will check again next time.") - } - } -} - -suspend fun Session.getSupportedPSTN(maxTries: Int): String? { - val thirdPartyProtocols: Map = try { - thirdPartyService().getThirdPartyProtocols() - } catch (failure: Throwable) { - if (maxTries == 1) { - throw failure - } else { - // Wait for 10s before trying again - delay(10_000L) - return getSupportedPSTN(maxTries - 1) - } - } - return when { - thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY - thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY - else -> null - } -} diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index c72d7c8a76b..469fba4d5e0 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent import org.matrix.android.sdk.api.session.room.model.call.SdpType -import org.matrix.android.sdk.internal.util.awaitCallback import org.threeten.bp.Duration import org.webrtc.AudioSource import org.webrtc.AudioTrack @@ -420,9 +419,7 @@ class WebRtcCall(val mxCall: MxCall, private suspend fun getTurnServer(): TurnServerResponse? { return tryOrNull { - awaitCallback { - sessionProvider.get()?.callSignalingService()?.getTurnServer(it) - } + sessionProvider.get()?.callSignalingService()?.getTurnServer() } } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index a2b82de0bb2..2f8f84051e4 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall +import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent @@ -57,34 +58,32 @@ import javax.inject.Singleton @Singleton class WebRtcCallManager @Inject constructor( private val context: Context, - private val activeSessionDataSource: ActiveSessionDataSource, - private val pstnProtocolChecker: PSTNProtocolChecker + private val activeSessionDataSource: ActiveSessionDataSource ) : CallListener, LifecycleObserver { private val currentSession: Session? get() = activeSessionDataSource.currentValue?.orNull() + private val pstnProtocolChecker: PSTNProtocolChecker? + get() = currentSession?.callSignalingService()?.getPSTNProtocolChecker() + interface CurrentCallListener { fun onCurrentCallChange(call: WebRtcCall?) {} fun onAudioDevicesChange() {} } - interface PSTNSupportListener { - fun onPSTNSupportUpdated() - } - val supportedPSTNProtocol: String? - get() = pstnProtocolChecker.supportedPSTNProtocol + get() = pstnProtocolChecker?.supportedPSTNProtocol val supportsPSTNProtocol: Boolean get() = supportedPSTNProtocol != null - fun addPstnSupportListener(listener: PSTNSupportListener) { - pstnProtocolChecker.addPstnSupportListener(listener) + fun addPstnSupportListener(listener: PSTNProtocolChecker.Listener) { + pstnProtocolChecker?.addListener(listener) } - fun removePstnSupportListener(listener: PSTNSupportListener) { - pstnProtocolChecker.removePstnSupportListener(listener) + fun removePstnSupportListener(listener: PSTNProtocolChecker.Listener) { + pstnProtocolChecker?.removeListener(listener) } private val currentCallsListeners = CopyOnWriteArrayList() @@ -116,7 +115,6 @@ class WebRtcCallManager @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { isInBackground = false - checkForPSTNSupportIfNeeded() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @@ -157,7 +155,7 @@ class WebRtcCallManager @Inject constructor( } fun checkForPSTNSupportIfNeeded() { - pstnProtocolChecker.checkForPSTNSupportIfNeeded(currentSession) + pstnProtocolChecker?.checkForPSTNSupportIfNeeded() } /** @@ -169,7 +167,6 @@ class WebRtcCallManager @Inject constructor( Timber.v("## VOIP headSetButtonTapped") val call = getCurrentCall() ?: return if (call.mxCall.state is CallState.LocalRinging) { - // accept call call.acceptIncomingCall() } if (call.mxCall.state is CallState.Connected) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index acc295b2427..b4ac226db42 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -26,8 +26,8 @@ import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.PublishRelay import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel @@ -65,6 +65,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho @@ -121,7 +122,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val directRoomHelper: DirectRoomHelper, timelineSettingsFactory: TimelineSettingsFactory ) : VectorViewModel(initialState), - Timeline.Listener, ChatEffectManager.Delegate, WebRtcCallManager.PSTNSupportListener { + Timeline.Listener, ChatEffectManager.Delegate, PSTNProtocolChecker.Listener { private val room = session.getRoom(initialState.roomId)!! private val eventId = initialState.eventId @@ -233,65 +234,65 @@ class RoomDetailViewModel @AssistedInject constructor( override fun handle(action: RoomDetailAction) { when (action) { - is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) - is RoomDetailAction.SaveDraft -> handleSaveDraft(action) - is RoomDetailAction.SendMessage -> handleSendMessage(action) - is RoomDetailAction.SendMedia -> handleSendMedia(action) - is RoomDetailAction.SendSticker -> handleSendSticker(action) - is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) - is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) - is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) - is RoomDetailAction.SendReaction -> handleSendReaction(action) - is RoomDetailAction.AcceptInvite -> handleAcceptInvite() - is RoomDetailAction.RejectInvite -> handleRejectInvite() - is RoomDetailAction.RedactAction -> handleRedactEvent(action) - is RoomDetailAction.UndoReaction -> handleUndoReact(action) - is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) - is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) - is RoomDetailAction.EnterEditMode -> handleEditAction(action) - is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) - is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) - is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) - is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) - is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) - is RoomDetailAction.ResendMessage -> handleResendEvent(action) - is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) - is RoomDetailAction.ResendAll -> handleResendAll() - is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() - is RoomDetailAction.ReportContent -> handleReportContent(action) - is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) + is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) + is RoomDetailAction.SaveDraft -> handleSaveDraft(action) + is RoomDetailAction.SendMessage -> handleSendMessage(action) + is RoomDetailAction.SendMedia -> handleSendMedia(action) + is RoomDetailAction.SendSticker -> handleSendSticker(action) + is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) + is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) + is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) + is RoomDetailAction.SendReaction -> handleSendReaction(action) + is RoomDetailAction.AcceptInvite -> handleAcceptInvite() + is RoomDetailAction.RejectInvite -> handleRejectInvite() + is RoomDetailAction.RedactAction -> handleRedactEvent(action) + is RoomDetailAction.UndoReaction -> handleUndoReact(action) + is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) + is RoomDetailAction.EnterEditMode -> handleEditAction(action) + is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) + is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) + is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) + is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) + is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) + is RoomDetailAction.ResendMessage -> handleResendEvent(action) + is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) + is RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() + is RoomDetailAction.ReportContent -> handleReportContent(action) + is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() - is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() - is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) - is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) - is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) - is RoomDetailAction.RequestVerification -> handleRequestVerification(action) - is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) - is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) - is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) - is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() - is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() - is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action) - is RoomDetailAction.StartCall -> handleStartCall(action) - is RoomDetailAction.AcceptCall -> handleAcceptCall(action) - is RoomDetailAction.EndCall -> handleEndCall() - is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() - is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) - is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) - is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) - is RoomDetailAction.CancelSend -> handleCancel(action) - is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) - is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) - RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() - RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() - is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) - RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) - is RoomDetailAction.ShowRoomAvatarFullScreen -> { + is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() + is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) + is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) + is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) + is RoomDetailAction.RequestVerification -> handleRequestVerification(action) + is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) + is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) + is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) + is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() + is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() + is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action) + is RoomDetailAction.StartCall -> handleStartCall(action) + is RoomDetailAction.AcceptCall -> handleAcceptCall(action) + is RoomDetailAction.EndCall -> handleEndCall() + is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() + is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) + is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) + is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) + is RoomDetailAction.CancelSend -> handleCancel(action) + is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) + is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) + RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() + RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() + is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) + RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) + is RoomDetailAction.ShowRoomAvatarFullScreen -> { _viewEvents.post( RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView) ) } - is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) + is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) }.exhaustive } @@ -620,16 +621,16 @@ class RoomDetailViewModel @AssistedInject constructor( return@withState false } when (itemId) { - R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true + R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.timeline_setting -> true - R.id.invite -> state.canInvite - R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true + R.id.invite -> state.canInvite + R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.open_matrix_apps -> true R.id.voice_call, - R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() - R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty() - R.id.search -> true - else -> false + R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() + R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty() + R.id.search -> true + else -> false } } @@ -742,7 +743,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendChatEffect -> { + is ParsedCommand.SendChatEffect -> { sendChatEffect(slashCommandResult) _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() @@ -775,7 +776,7 @@ class RoomDetailViewModel @AssistedInject constructor( } }.exhaustive } - is SendMode.EDIT -> { + is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId if (inReplyTo != null) { @@ -800,7 +801,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.QUOTE -> { + is SendMode.QUOTE -> { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() ?: state.sendMode.timelineEvent.root.getClearContent().toModel() @@ -823,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.REPLY -> { + is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(RoomDetailViewEvents.MessageSent) @@ -1442,7 +1443,7 @@ class RoomDetailViewModel @AssistedInject constructor( } override fun onPSTNSupportUpdated() { - updateShowDialerOptionState() + updateShowDialerOptionState() } private fun updateShowDialerOptionState() { From 79acf1cc42a5a611cca647a89194664203a180d2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 16 Feb 2021 15:57:23 +0100 Subject: [PATCH 06/10] Fix copyright --- .../matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt index 41486fad5a1..db260907308 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 754dec949bb561833d77bfb1add479448562f097 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 16 Feb 2021 20:45:22 +0100 Subject: [PATCH 07/10] Remove useless log --- .../helper/TimelineControllerInterceptorHelper.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 5eddedae2fb..6226ab7addb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -17,18 +17,17 @@ package im.vector.app.features.home.room.detail.timeline.helper import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.VisibilityState +import im.vector.app.core.epoxy.LoadingItem_ +import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.detail.UnreadState import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import com.airbnb.epoxy.VisibilityState import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem -import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.home.room.detail.timeline.item.IsEventItem import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ +import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.room.timeline.Timeline -import im.vector.app.core.epoxy.LoadingItem_ -import im.vector.app.core.epoxy.TimelineEmptyItem_ -import im.vector.app.features.home.room.detail.timeline.item.IsEventItem -import timber.log.Timber import kotlin.reflect.KMutableProperty0 private const val DEFAULT_PREFETCH_THRESHOLD = 30 @@ -62,7 +61,6 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut val firstUnreadEventId = (unreadState as? UnreadState.HasUnread)?.firstUnreadEventId // Then iterate on models so we have the exact positions in the adapter modelsIterator.forEach { epoxyModel -> - Timber.v("Index of model:${epoxyModel::class}: $index") if (epoxyModel is IsEventItem) { epoxyModel.getEventIds().forEach { eventId -> adapterPositionMapping[eventId] = index From 373586c23e92bdd8b8619b2e28bac44642515ff3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Feb 2021 10:25:06 +0100 Subject: [PATCH 08/10] Cleanup --- .idea/dictionaries/bmarty.xml | 1 + .../sdk/api/session/call/PSTNProtocolChecker.kt | 2 +- .../app/features/call/dialpad/DialPadLookup.kt | 16 +++++++++------- .../room/detail/ScrollOnNewMessageCallback.kt | 2 +- .../room/detail/timeline/item/IsEventItem.kt | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index 16cc35cebe6..4de90e94052 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -26,6 +26,7 @@ pkcs previewable previewables + pstn riotx signin signout diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt index db260907308..6627f62e24a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt @@ -45,7 +45,7 @@ class PSTNProtocolChecker @Inject internal constructor(private val taskExecutor: private var alreadyChecked = AtomicBoolean(false) - private val pstnSupportListeners = emptyList().toMutableList() + private val pstnSupportListeners = mutableListOf() fun addListener(listener: Listener) { pstnSupportListeners.add(listener) diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt index 1c5caee2cd9..6fccea6c8c4 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt @@ -22,20 +22,22 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import javax.inject.Inject -class DialPadLookup @Inject constructor(val session: Session, - val directRoomHelper: DirectRoomHelper, - val callManager: WebRtcCallManager +class DialPadLookup @Inject constructor( + private val session: Session, + private val directRoomHelper: DirectRoomHelper, + private val callManager: WebRtcCallManager ) { - class Failure : Throwable() + data class Result(val userId: String, val roomId: String) suspend fun lookupPhoneNumber(phoneNumber: String): Result { val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure() val thirdPartyUser = tryOrNull { - session.thirdPartyService().getThirdPartyUser(supportedProtocolKey, fields = mapOf( - "m.id.phone" to phoneNumber - )).firstOrNull() + session.thirdPartyService().getThirdPartyUser( + protocol = supportedProtocolKey, + fields = mapOf("m.id.phone" to phoneNumber) + ).firstOrNull() } ?: throw Failure() val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt index e7543278ab6..1ab8762895b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt @@ -48,7 +48,7 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager, return } val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? IsEventItem ?: return - val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() + val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() ?: return val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds) if (indexOfFirstNewItem != -1) { Timber.v("Should scroll to position: $position") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt index 059bdbea43f..4c2eeea48b3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item interface IsEventItem { /** * Returns the eventIds associated with the EventItem. - * Will generally get only one, but it handles the merging items. + * Will generally get only one, but it handles the merged items. */ fun getEventIds(): List } From a34c072c488647bf0968af4c39f0090ca50622e3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Feb 2021 10:28:49 +0100 Subject: [PATCH 09/10] Rename interface --- .../main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt | 4 ++-- .../features/home/room/detail/ScrollOnNewMessageCallback.kt | 4 ++-- .../timeline/helper/TimelineControllerInterceptorHelper.kt | 4 ++-- .../features/home/room/detail/timeline/item/BaseEventItem.kt | 2 +- .../timeline/item/{IsEventItem.kt => ItemWithEvents.kt} | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/{IsEventItem.kt => ItemWithEvents.kt} (96%) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt index 2339287cbe2..b77670ba760 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt @@ -19,10 +19,10 @@ package im.vector.app.core.epoxy import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.features.home.room.detail.timeline.item.IsEventItem +import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents @EpoxyModelClass(layout = R.layout.item_timeline_empty) -abstract class TimelineEmptyItem : VectorEpoxyModel(), IsEventItem { +abstract class TimelineEmptyItem : VectorEpoxyModel(), ItemWithEvents { @EpoxyAttribute lateinit var eventId: String diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt index 1ab8762895b..fbf9ebe32fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail import androidx.recyclerview.widget.LinearLayoutManager import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.item.IsEventItem +import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents import timber.log.Timber import java.util.concurrent.CopyOnWriteArrayList @@ -47,7 +47,7 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager, if (layoutManager.findFirstVisibleItemPosition() != position) { return } - val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? IsEventItem ?: return + val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? ItemWithEvents ?: return val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() ?: return val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds) if (indexOfFirstNewItem != -1) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 6226ab7addb..971a3a35d83 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -24,7 +24,7 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.detail.UnreadState import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem -import im.vector.app.features.home.room.detail.timeline.item.IsEventItem +import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -61,7 +61,7 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut val firstUnreadEventId = (unreadState as? UnreadState.HasUnread)?.firstUnreadEventId // Then iterate on models so we have the exact positions in the adapter modelsIterator.forEach { epoxyModel -> - if (epoxyModel is IsEventItem) { + if (epoxyModel is ItemWithEvents) { epoxyModel.getEventIds().forEach { eventId -> adapterPositionMapping[eventId] = index if (eventId == firstUnreadEventId) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index 8546ed7e0f2..13bb6db6efc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -32,7 +32,7 @@ import im.vector.app.core.utils.DimensionConverter /** * Children must override getViewType() */ -abstract class BaseEventItem : VectorEpoxyModel(), IsEventItem { +abstract class BaseEventItem : VectorEpoxyModel(), ItemWithEvents { // To use for instance when opening a permalink with an eventId @EpoxyAttribute diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt similarity index 96% rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt index 4c2eeea48b3..cf4211bb2cb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/IsEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.item -interface IsEventItem { +interface ItemWithEvents { /** * Returns the eventIds associated with the EventItem. * Will generally get only one, but it handles the merged items. From c787de75f53b3e596ae2731809f43aac67e12ac7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Feb 2021 10:36:33 +0100 Subject: [PATCH 10/10] Cleaup --- .../home/room/detail/timeline/factory/TimelineItemFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 18ffa995f89..7fd50147d43 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -115,7 +115,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me Timber.e(throwable, "failed to create message item") defaultItemFactory.create(event, highlight, callback, throwable) } - return (computedModel ?: buildEmptyItem(event)) + return computedModel ?: buildEmptyItem(event) } private fun buildEmptyItem(timelineEvent: TimelineEvent): TimelineEmptyItem {