diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 871c5378a6..b7377df1b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -125,6 +125,12 @@ interface RoomService { */ suspend fun deleteRoomAlias(roomAlias: String) + /** + * Return the current local changes membership for the given room. + * see [getChangeMembershipsLive] for more details. + */ + fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState + /** * Return a live data of all local changes membership that happened since the session has been opened. * It allows you to track this in your client to known what is currently being processed by the SDK. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index d9fe1288e2..632ea4c450 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor( deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias)) } + override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState { + return roomChangeMembershipStateDataSource.getState(roomIdOrAlias) + } + override fun getChangeMembershipsLive(): LiveData> { return roomChangeMembershipStateDataSource.getLiveStates() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt index b9c547d4fb..35d8cb08af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.session.SessionScope +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject /** @@ -30,7 +31,7 @@ import javax.inject.Inject internal class RoomChangeMembershipStateDataSource @Inject constructor() { private val mutableLiveStates = MutableLiveData>(emptyMap()) - private val states = HashMap() + private val states = ConcurrentHashMap() /** * This will update local states to be synced with the server. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 33776e4f6e..562b25683b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor( ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { + val currentState = roomChangeMembershipStateDataSource.getState(params.roomIdOrAlias) + if (currentState.isInProgress() || currentState == ChangeMembershipState.Joined) { + return + } roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { executeRequest(globalErrorReceiver) { diff --git a/newsfragment/3531.feature b/newsfragment/3531.feature new file mode 100644 index 0000000000..e8b63584e3 --- /dev/null +++ b/newsfragment/3531.feature @@ -0,0 +1 @@ +Introduces AutoAcceptInvites which can be enabled at compile time. \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index c5eac7e3e0..06174b9573 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -49,13 +49,12 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr // TODO Keep this class for now, will maybe be used fro Space @Singleton class AppStateHandler @Inject constructor( - sessionDataSource: ActiveSessionDataSource, + private val sessionDataSource: ActiveSessionDataSource, private val uiStateRepository: UiStateRepository, private val activeSessionHolder: ActiveSessionHolder ) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() - private val selectedSpaceDataSource = BehaviorDataSource>(Option.empty()) val selectedRoomGroupingObservable = selectedSpaceDataSource.observe() @@ -92,11 +91,11 @@ class AppStateHandler @Inject constructor( } } - init { + private fun observeActiveSession() { sessionDataSource.observe() .distinctUntilChanged() .subscribe { - // sessionDataSource could already return a session while acitveSession holder still returns null + // sessionDataSource could already return a session while activeSession holder still returns null it.orNull()?.let { session -> if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session) @@ -119,6 +118,7 @@ class AppStateHandler @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { + observeActiveSession() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @@ -126,7 +126,7 @@ class AppStateHandler @Inject constructor( compositeDisposable.clear() val session = activeSessionHolder.getSafeActiveSession() ?: return when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { - is RoomGroupingMethod.BySpace -> { + is RoomGroupingMethod.BySpace -> { uiStateRepository.storeGroupingMethod(true, session.sessionId) uiStateRepository.storeSelectedSpace(currentMethod.spaceSummary?.roomId, session.sessionId) } diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index f3e2f8740e..4791c2e499 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -47,6 +47,7 @@ import im.vector.app.core.rx.RxConfig import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog +import im.vector.app.features.invite.InvitesAcceptor import im.vector.app.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationUtils @@ -95,6 +96,7 @@ class VectorApplication : @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var pinLocker: PinLocker @Inject lateinit var callManager: WebRtcCallManager + @Inject lateinit var invitesAcceptor: InvitesAcceptor lateinit var vectorComponent: VectorComponent @@ -116,6 +118,7 @@ class VectorApplication : appContext = this vectorComponent = DaggerVectorComponent.factory().create(this) vectorComponent.inject(this) + invitesAcceptor.initialize() vectorUncaughtExceptionHandler.activate(this) rxConfig.setupRxPlugin() diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 3c11bfcd13..38edb771bb 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.home.room.list.RoomListModule import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.InviteUsersToRoomActivity import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.link.LinkHandlerActivity @@ -122,6 +123,7 @@ interface ScreenComponent { fun errorFormatter(): ErrorFormatter fun uiStateRepository(): UiStateRepository fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog + fun autoAcceptInvites(): AutoAcceptInvites /* ========================================================================================== * Activities diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index e5a47e872c..4a3379cb5a 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -42,6 +42,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorPr import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.VectorHtmlCompressor +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.navigation.Navigator import im.vector.app.features.notifications.NotifiableEventResolver @@ -160,6 +161,8 @@ interface VectorComponent { fun pinLocker(): PinLocker + fun autoAcceptInvites(): AutoAcceptInvites + fun webRtcCallManager(): WebRtcCallManager fun roomSummaryHolder(): RoomSummariesHolder diff --git a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt index 77cad0ae73..006a2f5aa0 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt @@ -25,6 +25,8 @@ import dagger.Module import dagger.Provides import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter +import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.navigation.DefaultNavigator import im.vector.app.features.navigation.Navigator import im.vector.app.features.pin.PinCodeStore @@ -105,4 +107,7 @@ abstract class VectorModule { @Binds abstract fun bindPinCodeStore(store: SharedPrefPinCodeStore): PinCodeStore + + @Binds + abstract fun bindAutoAcceptInvites(autoAcceptInvites: CompileTimeAutoAcceptInvites): AutoAcceptInvites } diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt index aa7654c405..0820b34124 100644 --- a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt +++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt @@ -67,10 +67,6 @@ class CallUserMapper(private val session: Session, private val protocolsChecker: // will make sure we know where how to map calls and also allow us know not to display // it in the future. invitedRoom.markVirtual(nativeRoomId) - // also auto-join the virtual room if we have a matching native room - // (possibly we should only join if we've also joined the native room, then we'd also have - // to make sure we joined virtual rooms on joining a native one) - session.joinRoom(invitedRoomId) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index b6210ae019..b960402f90 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -31,6 +31,8 @@ import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.createdirect.DirectRoomHelper +import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.invite.showInvites import im.vector.app.features.ui.UiStateRepository import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers @@ -56,7 +58,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private val uiStateRepository: UiStateRepository, private val callManager: WebRtcCallManager, private val directRoomHelper: DirectRoomHelper, - private val appStateHandler: AppStateHandler) + private val appStateHandler: AppStateHandler, +private val autoAcceptInvites: AutoAcceptInvites) : VectorViewModel(initialState), CallProtocolsChecker.Listener { @@ -204,21 +207,25 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } is RoomGroupingMethod.BySpace -> { val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId - val dmInvites = session.getRoomSummaries( - roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - activeSpaceFilter = activeSpaceRoomId?.let { ActiveSpaceFilter.ActiveSpace(it) } ?: ActiveSpaceFilter.None - } - ).size + var dmInvites = 0 + var roomsInvite = 0 + if (autoAcceptInvites.showInvites()) { + dmInvites = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + activeSpaceFilter = activeSpaceRoomId?.let { ActiveSpaceFilter.ActiveSpace(it) } ?: ActiveSpaceFilter.None + } + ).size - val roomsInvite = session.getRoomSummaries( - roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId) - } - ).size + roomsInvite = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId) + } + ).size + } val dmRooms = session.getNotificationCountForRooms( roomSummaryQueryParams { diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 9b506b6ed7..e9e2447b39 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import io.reactivex.Observable import io.reactivex.schedulers.Schedulers @@ -54,7 +55,8 @@ data class CountInfo( class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState, session: Session, private val vectorPreferences: VectorPreferences, - appStateHandler: AppStateHandler) + appStateHandler: AppStateHandler, + private val autoAcceptInvites: AutoAcceptInvites) : VectorViewModel(initialState) { @AssistedFactory @@ -92,12 +94,17 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) } ) - val invites = session.getRoomSummaries( - roomSummaryQueryParams { - this.memberships = listOf(Membership.INVITE) - this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) - } - ).size + val invites = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) + } + ).size + } + copy( homeSpaceUnread = RoomAggregateNotificationCount( counts.notificationCount + invites, @@ -129,10 +136,13 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia is RoomGroupingMethod.BySpace -> { val selectedSpace = appStateHandler.safeActiveSpaceId() - val inviteCount = session.getRoomSummaries( - roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - ).size - + val inviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt index 22b0eb091c..106a02cd3c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt @@ -22,6 +22,8 @@ import im.vector.app.R import im.vector.app.RoomGroupingMethod import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.invite.showInvites import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope @@ -38,6 +40,7 @@ class GroupRoomListSectionBuilder( val stringProvider: StringProvider, val viewModelScope: CoroutineScope, val appStateHandler: AppStateHandler, + private val autoAcceptInvites: AutoAcceptInvites, val onDisposable: (Disposable) -> Unit, val onUdpatable: (UpdatableLivePageResult) -> Unit ) : RoomListSectionBuilder { @@ -48,15 +51,15 @@ class GroupRoomListSectionBuilder( val actualGroupId = appStateHandler.safeActiveGroupId() when (mode) { - RoomListDisplayMode.PEOPLE -> { + RoomListDisplayMode.PEOPLE -> { // 3 sections Invites / Fav / Dms buildPeopleSections(sections, activeGroupAwareQueries, actualGroupId) } - RoomListDisplayMode.ROOMS -> { + RoomListDisplayMode.ROOMS -> { // 5 sections invites / Fav / Rooms / Low Priority / Server notice buildRoomsSections(sections, activeGroupAwareQueries, actualGroupId) } - RoomListDisplayMode.FILTERED -> { + RoomListDisplayMode.FILTERED -> { // Used when searching for rooms withQueryParams( { @@ -73,17 +76,18 @@ class GroupRoomListSectionBuilder( ) } RoomListDisplayMode.NOTIFICATIONS -> { - addSection( - sections, - activeGroupAwareQueries, - R.string.invitations_header, - true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ALL - it.activeGroupId = actualGroupId + if (autoAcceptInvites.showInvites()) { + addSection( + sections, + activeGroupAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ALL + it.activeGroupId = actualGroupId + } } - addSection( sections, activeGroupAwareQueries, @@ -115,15 +119,17 @@ class GroupRoomListSectionBuilder( private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList, actualGroupId: String?) { - addSection( - sections, - activeSpaceAwareQueries, - R.string.invitations_header, - true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - it.activeGroupId = actualGroupId + if (autoAcceptInvites.showInvites()) { + addSection( + sections, + activeSpaceAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.activeGroupId = actualGroupId + } } addSection( @@ -180,14 +186,16 @@ class GroupRoomListSectionBuilder( activeSpaceAwareQueries: MutableList, actualGroupId: String? ) { - addSection(sections, - activeSpaceAwareQueries, - R.string.invitations_header, - true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM - it.activeGroupId = actualGroupId + if (autoAcceptInvites.showInvites()) { + addSection(sections, + activeSpaceAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + it.activeGroupId = actualGroupId + } } addSection( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index fbb8faebb0..c5f166ea5b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -30,6 +30,7 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -49,7 +50,8 @@ class RoomListViewModel @Inject constructor( private val session: Session, private val stringProvider: StringProvider, private val appStateHandler: AppStateHandler, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites ) : VectorViewModel(initialState) { interface Factory { @@ -126,6 +128,7 @@ class RoomListViewModel @Inject constructor( appStateHandler, viewModelScope, suggestedRoomJoiningState, + autoAcceptInvites, { it.disposeOnClear() }, @@ -140,6 +143,7 @@ class RoomListViewModel @Inject constructor( stringProvider, viewModelScope, appStateHandler, + autoAcceptInvites, { it.disposeOnClear() }, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt index a30c175f41..6b269356c7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.list import im.vector.app.AppStateHandler import im.vector.app.core.resources.StringProvider +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -26,16 +27,18 @@ import javax.inject.Provider class RoomListViewModelFactory @Inject constructor(private val session: Provider, private val appStateHandler: AppStateHandler, private val stringProvider: StringProvider, - private val vectorPreferences: VectorPreferences) + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites) : RoomListViewModel.Factory { override fun create(initialState: RoomListViewState): RoomListViewModel { return RoomListViewModel( - initialState, - session.get(), - stringProvider, - appStateHandler, - vectorPreferences + initialState = initialState, + session = session.get(), + stringProvider = stringProvider, + appStateHandler = appStateHandler, + vectorPreferences = vectorPreferences, + autoAcceptInvites = autoAcceptInvites ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index 266adf6b0c..5a296ce7ed 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -26,6 +26,8 @@ import im.vector.app.AppStateHandler import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.invite.showInvites import im.vector.app.space import io.reactivex.Observable import io.reactivex.disposables.Disposable @@ -50,6 +52,7 @@ class SpaceRoomListSectionBuilder( val appStateHandler: AppStateHandler, val viewModelScope: CoroutineScope, private val suggestedRoomJoiningState: LiveData>>, + private val autoAcceptInvites: AutoAcceptInvites, val onDisposable: (Disposable) -> Unit, val onUdpatable: (UpdatableLivePageResult) -> Unit, val onlyOrphansInHome: Boolean = false @@ -66,13 +69,13 @@ class SpaceRoomListSectionBuilder( val sections = mutableListOf() val activeSpaceAwareQueries = mutableListOf() when (mode) { - RoomListDisplayMode.PEOPLE -> { + RoomListDisplayMode.PEOPLE -> { buildDmSections(sections, activeSpaceAwareQueries) } - RoomListDisplayMode.ROOMS -> { + RoomListDisplayMode.ROOMS -> { buildRoomsSections(sections, activeSpaceAwareQueries) } - RoomListDisplayMode.FILTERED -> { + RoomListDisplayMode.FILTERED -> { withQueryParams( { it.memberships = Membership.activeMemberships() @@ -88,20 +91,22 @@ class SpaceRoomListSectionBuilder( ) } RoomListDisplayMode.NOTIFICATIONS -> { - addSection( - sections = sections, - activeSpaceUpdaters = activeSpaceAwareQueries, - nameRes = R.string.invitations_header, - notifyOfLocalEcho = true, - spaceFilterStrategy = if (onlyOrphansInHome) { - RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL - } else { - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL - }, - countRoomAsNotif = true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ALL + if (autoAcceptInvites.showInvites()) { + addSection( + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + }, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ALL + } } addSection( @@ -136,16 +141,18 @@ class SpaceRoomListSectionBuilder( } private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { - addSection( - sections = sections, - activeSpaceUpdaters = activeSpaceAwareQueries, - nameRes = R.string.invitations_header, - notifyOfLocalEcho = true, - spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, - countRoomAsNotif = true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + if (autoAcceptInvites.showInvites()) { + addSection( + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } } addSection( @@ -253,15 +260,17 @@ class SpaceRoomListSectionBuilder( } private fun buildDmSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { - addSection(sections = sections, - activeSpaceUpdaters = activeSpaceAwareQueries, - nameRes = R.string.invitations_header, - notifyOfLocalEcho = true, - spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, - countRoomAsNotif = true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + if (autoAcceptInvites.showInvites()) { + addSection(sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } } addSection(sections, @@ -387,7 +396,7 @@ class SpaceRoomListSectionBuilder( activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) ) } - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { if (currentSpace == null) { copy( activeSpaceFilter = ActiveSpaceFilter.None @@ -398,7 +407,7 @@ class SpaceRoomListSectionBuilder( ) } } - RoomListViewModel.SpaceFilterStrategy.NONE -> this + RoomListViewModel.SpaceFilterStrategy.NONE -> this } } } diff --git a/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt new file mode 100644 index 0000000000..87febb37bc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt @@ -0,0 +1,45 @@ +/* + * 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.invite + +import javax.inject.Inject + +/** + * This interface defines 2 flags so you can handle auto accept invites. + * At the moment we only have [CompileTimeAutoAcceptInvites] implementation. + */ +interface AutoAcceptInvites { + /** + * Enable auto-accept invites. It means, as soon as you got an invite from the sync, it will try to join it. + */ + val isEnabled: Boolean + + /** + * Hide invites from the UI (from notifications, notification count and room list). By default invites are hidden when [isEnabled] is true + */ + val hideInvites: Boolean + get() = isEnabled +} + +fun AutoAcceptInvites.showInvites() = !hideInvites + +/** + * Simple compile time implementation of AutoAcceptInvites flags. + */ +class CompileTimeAutoAcceptInvites @Inject constructor() : AutoAcceptInvites { + override val isEnabled = false +} diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt new file mode 100644 index 0000000000..6e7de1c35b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -0,0 +1,143 @@ +/* + * 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.invite + +import im.vector.app.ActiveSessionDataSource +import im.vector.app.features.session.coroutineScope +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.rx.rx +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** + * This class is responsible for auto accepting invites. + * It's listening to invites and membershipChanges so it can retry automatically if needed. + * This mechanism will be on only if AutoAcceptInvites.isEnabled is true. + */ +@Singleton +class InvitesAcceptor @Inject constructor( + private val sessionDataSource: ActiveSessionDataSource, + private val autoAcceptInvites: AutoAcceptInvites +) : Session.Listener { + + private lateinit var activeSessionDisposable: Disposable + private val shouldRejectRoomIds = mutableSetOf() + private val invitedRoomDisposables = HashMap() + private val semaphore = Semaphore(1) + + fun initialize() { + observeActiveSession() + } + + private fun observeActiveSession() { + activeSessionDisposable = sessionDataSource.observe() + .distinctUntilChanged() + .subscribe { + it.orNull()?.let { session -> + onSessionActive(session) + } + } + } + + private fun onSessionActive(session: Session) { + if (!autoAcceptInvites.isEnabled) { + return + } + if (invitedRoomDisposables.containsKey(session.sessionId)) { + return + } + session.addListener(this) + val roomQueryParams = roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + } + val rxSession = session.rx() + Observable + .combineLatest( + rxSession.liveRoomSummaries(roomQueryParams), + rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS), + { invitedRooms, _ -> invitedRooms.map { it.roomId } } + ) + .filter { it.isNotEmpty() } + .subscribe { invitedRoomIds -> + session.coroutineScope.launch { + semaphore.withPermit { + Timber.v("Invited roomIds: $invitedRoomIds") + for (roomId in invitedRoomIds) { + async { session.joinRoomSafely(roomId) }.start() + } + } + } + } + .also { + invitedRoomDisposables[session.sessionId] = it + } + } + + private suspend fun Session.joinRoomSafely(roomId: String) { + if (shouldRejectRoomIds.contains(roomId)) { + getRoom(roomId)?.rejectInviteSafely() + return + } + val roomMembershipChanged = getChangeMemberships(roomId) + if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) { + try { + Timber.v("Try auto join room: $roomId") + joinRoom(roomId) + } catch (failure: Throwable) { + Timber.v("Failed auto join room: $roomId") + // if we got 404 on invites, the inviting user have left or the hs is off. + if (failure is Failure.ServerError && failure.httpCode == 404) { + val room = getRoom(roomId) ?: return + val inviterId = room.roomSummary()?.inviterId + // if the inviting user is on the same HS, there can only be one cause: they left, so we try to reject the invite. + if (inviterId?.endsWith(sessionParams.credentials.homeServer.orEmpty()).orFalse()) { + shouldRejectRoomIds.add(roomId) + room.rejectInviteSafely() + } + } + } + } + } + + private suspend fun Room.rejectInviteSafely() { + try { + leave(null) + shouldRejectRoomIds.remove(roomId) + } catch (failure: Throwable) { + Timber.v("Fail rejecting invite for room: $roomId") + } + } + + override fun onSessionStopped(session: Session) { + session.removeListener(this) + invitedRoomDisposables.remove(session.sessionId)?.dispose() + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 7ac9b28b9a..37ed1e654a 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -27,6 +27,7 @@ import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.FirstThrottler +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -50,7 +51,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context private val activeSessionDataSource: ActiveSessionDataSource, private val iconLoader: IconLoader, private val bitmapLoader: BitmapLoader, - private val outdatedDetector: OutdatedEventDetector?) { + private val outdatedDetector: OutdatedEventDetector?, + private val autoAcceptInvites: AutoAcceptInvites) { private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY) private var backgroundHandler: Handler @@ -253,7 +255,14 @@ class NotificationDrawerManager @Inject constructor(private val context: Context roomEvents.add(event) } } - is InviteNotifiableEvent -> invitationEvents.add(event) + is InviteNotifiableEvent -> { + if (autoAcceptInvites.hideInvites) { + // Forget this event + eventIterator.remove() + } else { + invitationEvents.add(event) + } + } is SimpleNotifiableEvent -> simpleEvents.add(event) else -> Timber.w("Type not handled") } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index d30d20fdd4..400a658d1b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -28,6 +28,7 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.group @@ -61,7 +62,8 @@ import java.util.concurrent.TimeUnit class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, private val appStateHandler: AppStateHandler, private val session: Session, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites ) : VectorViewModel(initialState) { @AssistedFactory @@ -126,10 +128,13 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(Schedulers.computation()) .subscribe { - val inviteCount = session.getRoomSummaries( - roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - ).size - + val inviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN)