diff --git a/CHANGES.md b/CHANGES.md index a028ef6f1e..8a69aea6fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,11 +5,17 @@ Features ✨: - Enable url previews for notices (#2562) Improvements 🙌: - - + - Add System theme option and set as default (#904, #2387) + - Warn user when he is leaving a not public room (#1460) Bugfix 🐛: + - Unspecced msgType field in m.sticker (#2580) + - Wait for all room members to be known before sending a message to a e2e room (#2518) - Url previews sometimes attached to wrong message (#2561) - + - Room Topic not displayed correctly after visiting a link (#2551) + - Hiding membership events works the exact opposite (#2603) + - Tapping drawer having more than 1 room in notifications gives "malformed link" error (#2605) + - Initial sync is not retried correctly when there is some network error. (#2632) Translations 🗣: - @@ -17,7 +23,7 @@ SDK API changes ⚠️: - Build 🧱: - - + - Remove dependency to org.greenrobot.eventbus library Test: - diff --git a/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt index ae095be41a..9b1345cd39 100644 --- a/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt @@ -40,13 +40,16 @@ import kotlin.math.abs abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventListener { - lateinit var pager2: ViewPager2 - lateinit var imageTransitionView: ImageView - lateinit var transitionImageContainer: ViewGroup + protected val pager2: ViewPager2 + get() = views.attachmentPager + protected val imageTransitionView: ImageView + get() = views.transitionImageView + protected val transitionImageContainer: ViewGroup + get() = views.transitionImageContainer - var topInset = 0 - var bottomInset = 0 - var systemUiVisibility = true + private var topInset = 0 + private var bottomInset = 0 + private var systemUiVisibility = true private var overlayView: View? = null set(value) { @@ -65,14 +68,16 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi private lateinit var gestureDetector: GestureDetectorCompat var currentPosition = 0 + private set private var swipeDirection: SwipeDirection? = null private fun isScaled() = attachmentsAdapter.isScaled(currentPosition) + private val attachmentsAdapter = AttachmentsAdapter() + private var wasScaled: Boolean = false private var isSwipeToDismissAllowed: Boolean = true - private lateinit var attachmentsAdapter: AttachmentsAdapter private var isOverlayWasClicked = false // private val shouldDismissToBottom: Boolean @@ -101,10 +106,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi views = ActivityAttachmentViewerBinding.inflate(layoutInflater) setContentView(views.root) views.attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL - attachmentsAdapter = AttachmentsAdapter() views.attachmentPager.adapter = attachmentsAdapter - imageTransitionView = views.transitionImageView - pager2 = views.attachmentPager directionDetector = createSwipeDirectionDetector() gestureDetector = createGestureDetector() diff --git a/docs/ui-tests.md b/docs/ui-tests.md index ff01da0b31..6ebb52abe8 100644 --- a/docs/ui-tests.md +++ b/docs/ui-tests.md @@ -27,7 +27,6 @@ $ source env/bin/activate Every time you want to launch these test homeservers, type: ```shell script -$ virtualenv -p python3 env $ source env/bin/activate (env) $ demo/start.sh --no-rate-limit ``` diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index e068c17d20..acd3e11b2d 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -167,9 +167,6 @@ dependencies { implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' - // Bus - implementation 'org.greenrobot:eventbus:3.1.1' - // Phone number https://github.com/google/libphonenumber implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23' diff --git a/matrix-sdk-android/proguard-rules.pro b/matrix-sdk-android/proguard-rules.pro index fa860d8049..182f9473e8 100644 --- a/matrix-sdk-android/proguard-rules.pro +++ b/matrix-sdk-android/proguard-rules.pro @@ -20,14 +20,8 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile - -### EVENT BUS ### - +# BMA: Not sure I can delete this one without side effect -keepattributes *Annotation* --keepclassmembers class * { - @org.greenrobot.eventbus.Subscribe ; -} --keep enum org.greenrobot.eventbus.ThreadMode { *; } ### MOSHI ### diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 0e7088a6a5..cb49ee8818 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -86,7 +86,7 @@ class CommonTestHelper(context: Context) { * * @param session the session to sync */ - fun syncSession(session: Session) { + fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) { val lock = CountDownLatch(1) val job = GlobalScope.launch(Dispatchers.Main) { @@ -109,7 +109,7 @@ class CommonTestHelper(context: Context) { } GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) } - await(lock) + await(lock, timeout) } /** @@ -119,7 +119,7 @@ class CommonTestHelper(context: Context) { * @param message the message to send * @param nbOfMessages the number of time the message will be sent */ - fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List { + fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List { val timeline = room.createTimeline(null, TimelineSettings(10)) val sentEvents = ArrayList(nbOfMessages) val latch = CountDownLatch(1) @@ -151,7 +151,7 @@ class CommonTestHelper(context: Context) { room.sendTextMessage(message + " #" + (i + 1)) } // Wait 3 second more per message - await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages) + await(latch, timeout = timeout + 3_000L * nbOfMessages) timeline.dispose() // Check that all events has been created @@ -215,14 +215,14 @@ class CommonTestHelper(context: Context) { .getLoginFlow(hs, it) } - doSync { + doSync(timeout = 60_000) { matrix.authenticationService .getRegistrationWizard() .createAccount(userName, password, null, it) } // Perform dummy step - val registrationResult = doSync { + val registrationResult = doSync(timeout = 60_000) { matrix.authenticationService .getRegistrationWizard() .dummy(it) @@ -231,7 +231,7 @@ class CommonTestHelper(context: Context) { assertTrue(registrationResult is RegistrationResult.Success) val session = (registrationResult as RegistrationResult.Success).session if (sessionTestParams.withInitialSync) { - syncSession(session) + syncSession(session, 60_000) } return session diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt index 76e59d9a90..b6bedbd719 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt @@ -18,14 +18,21 @@ package org.matrix.android.sdk.common import org.matrix.android.sdk.api.session.Session -data class CryptoTestData(val firstSession: Session, - val roomId: String, - val secondSession: Session? = null, - val thirdSession: Session? = null) { +data class CryptoTestData(val roomId: String, + val sessions: List) { + + val firstSession: Session + get() = sessions.first() + + val secondSession: Session? + get() = sessions.getOrNull(1) + + val thirdSession: Session? + get() = sessions.getOrNull(2) fun cleanUp(testHelper: CommonTestHelper) { - testHelper.signOutAndClose(firstSession) - secondSession?.let { testHelper.signOutAndClose(it) } - thirdSession?.let { testHelper.signOutAndClose(it) } + sessions.forEach { + testHelper.signOutAndClose(it) + } } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index cbb22daf0f..3d5856fc64 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -73,7 +73,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } } - return CryptoTestData(aliceSession, roomId) + return CryptoTestData(roomId, listOf(aliceSession)) } /** @@ -139,7 +139,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // assertNotNull(roomFromBobPOV.powerLevels) // assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) - return CryptoTestData(aliceSession, aliceRoomId, bobSession) + return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession)) } /** @@ -157,7 +157,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // wait the initial sync SystemClock.sleep(1000) - return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession) + return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession)) } /** @@ -381,4 +381,30 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } } } + + fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData { + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) + aliceSession.cryptoService().setWarnOnUnknownDevices(false) + + val roomId = mTestHelper.doSync { + aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it) + } + val room = aliceSession.getRoom(roomId)!! + + mTestHelper.runBlockingTest { + room.enableEncryption() + } + + val sessions = mutableListOf(aliceSession) + for (index in 1 until numberOfMembers) { + val session = mTestHelper.createAccount("User_$index", defaultSessionParams) + mTestHelper.doSync(timeout = 600_000) { room.invite(session.myUserId, null, it) } + println("TEST -> " + session.myUserId + " invited") + mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } + println("TEST -> " + session.myUserId + " joined") + sessions.add(session) + } + + return CryptoTestData(roomId, sessions) + } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt new file mode 100644 index 0000000000..ff07cf1d1d --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2020 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. + * 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.session.room.timeline + +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestHelper +import java.util.concurrent.CountDownLatch +import kotlin.test.fail + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class TimelineWithManyMembersTest : InstrumentedTest { + + companion object { + private const val NUMBER_OF_MEMBERS = 6 + } + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + /** + * Ensures when someone sends a message to a crowded room, everyone can decrypt the message. + */ + @Test + fun everyone_should_decrypt_message_in_a_crowded_room() { + val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS) + + val sessionForFirstMember = cryptoTestData.firstSession + val roomForFirstMember = sessionForFirstMember.getRoom(cryptoTestData.roomId)!! + + val firstMessage = "First messages from Alice" + commonTestHelper.sendTextMessage( + roomForFirstMember, + firstMessage, + 1, + 600_000 + ) + + for (index in 1 until cryptoTestData.sessions.size) { + val session = cryptoTestData.sessions[index] + val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!! + val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30)) + timelineForCurrentMember.start() + + session.startSync(true) + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + snapshot + .find { it.isEncrypted() } + ?.let { + val body = it.root.getClearContent()?.toModel()?.body + if (body?.startsWith(firstMessage).orFalse()) { + println("User " + session.myUserId + " decrypted as " + body) + return@createEventListener true + } else { + fail("User " + session.myUserId + " decrypted as " + body + " CryptoError: " + it.root.mCryptoError) + } + } ?: return@createEventListener false + } + timelineForCurrentMember.addListener(eventsListener) + commonTestHelper.await(lock, 600_000) + } + session.stopSync() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index 725fd08d3b..93a1b962ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -35,7 +35,11 @@ data class MatrixConfiguration( * Optional proxy to connect to the matrix servers * You can create one using for instance Proxy(proxyType, InetSocketAddress.createUnresolved(hostname, port) */ - val proxy: Proxy? = null + val proxy: Proxy? = null, + /** + * True to advertise support for call transfers to other parties on Matrix calls. + */ + val supportsCallTransfer: Boolean = false ) { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 360b955869..bf21941e0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -41,6 +41,16 @@ interface AuthenticationService { */ fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback): Cancelable + /** + * Get a SSO url + */ + fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? + + /** + * Get the sign in or sign up fallback URL + */ + fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? + /** * Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index 75cff0e709..2b7c0a7578 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.call import org.matrix.android.sdk.api.session.room.model.call.CallCandidate +import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.util.Optional @@ -42,6 +43,8 @@ interface MxCall : MxCallDetail { var opponentPartyId: Optional? var opponentVersion: Int + var capabilities: CallCapabilities? + var state: CallState /** @@ -86,6 +89,11 @@ interface MxCall : MxCallDetail { */ fun sendLocalIceCandidateRemovals(candidates: List) + /** + * Send a m.call.replaces event to initiate call transfer. + */ + suspend fun transfer(targetUserId: String, targetRoomId: String?) + fun addListener(listener: StateListener) fun removeListener(listener: StateListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 85ba3100b0..3a4b4f82c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -72,6 +72,7 @@ object EventType { const val CALL_NEGOTIATE = "m.call.negotiate" const val CALL_REJECT = "m.call.reject" const val CALL_HANGUP = "m.call.hangup" + const val CALL_REPLACES = "m.call.replaces" // Key share events const val ROOM_KEY_REQUEST = "m.room_key_request" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt index aefc086b43..ac1d726d03 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt @@ -25,7 +25,6 @@ interface PermalinkService { companion object { const val MATRIX_TO_URL_BASE = "https://matrix.to/#/" - const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://" } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt index 1fe4c3576f..45a54bb379 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt @@ -39,7 +39,11 @@ data class CallAnswerContent( /** * Required. The version of the VoIP specification this messages adheres to. */ - @Json(name = "version") override val version: String? + @Json(name = "version") override val version: String?, + /** + * Capability advertisement. + */ + @Json(name = "capabilities") val capabilities: CallCapabilities? = null ): CallSignallingContent { @JsonClass(generateAdapter = true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt new file mode 100644 index 0000000000..d911ca3b88 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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. + * 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.room.model.call + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.extensions.orFalse + +@JsonClass(generateAdapter = true) +data class CallCapabilities( + /** + * If set to true, states that the sender of the event supports the m.call.replaces event and therefore supports + * being transferred to another destination + */ + @Json(name = "m.call.transferee") val transferee: Boolean? = null +) + +fun CallCapabilities?.supportCallTransfer() = this?.transferee.orFalse() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt index dfbc5c64be..42489bc0ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt @@ -49,7 +49,11 @@ data class CallInviteContent( /** * The field should be added for all invites where the target is a specific user */ - @Json(name = "invitee") val invitee: String? = null + @Json(name = "invitee") val invitee: String? = null, + /** + * Capability advertisement. + */ + @Json(name = "capabilities") val capabilities: CallCapabilities? = null ): CallSignallingContent { @JsonClass(generateAdapter = true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt new file mode 100644 index 0000000000..97a3b8c7a7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2020 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. + * 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.room.model.call + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This event is sent to signal the intent of a participant in a call to replace the call with another, + * such that the other participant ends up in a call with a new user. + */ +@JsonClass(generateAdapter = true) +data class CallReplacesContent( + /** + * Required. The ID of the call this event relates to. + */ + @Json(name = "call_id") override val callId: String, + /** + * Required. ID to let user identify remote echo of their own events + */ + @Json(name = "party_id") override val partyId: String? = null, + /** + * An identifier for the call replacement itself, generated by the transferor. + */ + @Json(name = "replacement_id") val replacementId: String? = null, + /** + * Optional. If specified, the transferee client waits for an invite to this room and joins it + * (possibly waiting for user confirmation) and then continues the transfer in this room. + * If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing. + */ + @Json(name = "target_room") val targerRoomId: String? = null, + /** + * An object giving information about the transfer target + */ + @Json(name = "target_user") val targetUser: TargetUser? = null, + /** + * If specified, gives the call ID for the transferee's client to use when placing the replacement call. + * Mutually exclusive with await_call + */ + @Json(name = "create_call") val createCall: String? = null, + /** + * If specified, gives the call ID that the transferee's client should wait for. + * Mutually exclusive with create_call. + */ + @Json(name = "await_call") val awaitCall: String? = null, + /** + * Required. The version of the VoIP specification this messages adheres to. + */ + @Json(name = "version") override val version: String? +): CallSignallingContent { + + @JsonClass(generateAdapter = true) + data class TargetUser( + /** + * Required. The matrix user ID of the transfer target + */ + @Json(name = "id") val id: String, + /** + * Optional. The display name of the transfer target. + */ + @Json(name = "display_name") val displayName: String?, + /** + * Optional. The avatar URL of the transfer target. + */ + @Json(name = "avatar_url") val avatarUrl: String? + + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt index 00fa68c0ac..280316d4b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt @@ -27,6 +27,7 @@ data class MessageStickerContent( /** * Set in local, not from server */ + @Transient override val msgType: String = MessageType.MSGTYPE_STICKER_LOCAL, /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt new file mode 100644 index 0000000000..c625a7f088 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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. + * 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.room.state + +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent + +/** + * Return true if a room can be joined by anyone (RoomJoinRules.PUBLIC) + */ +fun StateService.isPublic(): Boolean { + return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) + ?.content + ?.toModel() + ?.joinRules == RoomJoinRules.PUBLIC +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 73cb94b417..4216b03a87 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -52,6 +52,8 @@ data class TimelineEvent( } } + val roomId = root.roomId ?: "" + val metadata = HashMap() /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/UrlExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/UrlExtensions.kt new file mode 100644 index 0000000000..17f27b2514 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/UrlExtensions.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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. + * 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.util + +import java.net.URLEncoder + +/** + * Append param and value to a Url, using "?" or "&". Value parameter will be encoded + * Return this for chaining purpose + */ +fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder { + if (contains("?")) { + append("&") + } else { + append("?") + } + + append(param) + append("=") + append(URLEncoder.encode(value, "utf-8")) + + return this +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt similarity index 69% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/Constants.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt index 7d18aba627..642279cc27 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/Constants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt @@ -14,25 +14,25 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.auth +package org.matrix.android.sdk.internal.auth /** * Path to use when the client does not supported any or all login flows * Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback */ -const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/" +internal const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/" /** * Path to use when the client does not supported any or all registration flows * Not documented */ -const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/" +internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/" /** * Path to use when the client want to connect using SSO * Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login */ -const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect" -const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect" +internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect" +internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect" -const val SSO_REDIRECT_URL_PARAM = "redirectUrl" +internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 55f053de8d..c99e9bd81c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.NoOpCancellable +import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.RiotConfig @@ -99,6 +100,52 @@ internal class DefaultAuthenticationService @Inject constructor( } } + override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { + val homeServerUrlBase = getHomeServerUrlBase() ?: return null + + return buildString { + append(homeServerUrlBase) + if (providerId != null) { + append(MSC2858_SSO_REDIRECT_PATH) + append("/$providerId") + } else { + append(SSO_REDIRECT_PATH) + } + // Set the redirect url + appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl) + deviceId?.takeIf { it.isNotBlank() }?.let { + // But https://github.com/matrix-org/synapse/issues/5755 + appendParamToUrl("device_id", it) + } + } + } + + override fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? { + val homeServerUrlBase = getHomeServerUrlBase() ?: return null + + return buildString { + append(homeServerUrlBase) + if (forSignIn) { + append(LOGIN_FALLBACK_PATH) + deviceId?.takeIf { it.isNotBlank() }?.let { + // But https://github.com/matrix-org/synapse/issues/5755 + appendParamToUrl("device_id", it) + } + } else { + // For sign up + append(REGISTER_FALLBACK_PATH) + } + } + } + + private fun getHomeServerUrlBase(): String? { + return pendingSessionData + ?.homeServerConnectionConfig + ?.homeServerUri + ?.toString() + ?.trim { it == '/' } + } + override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback): Cancelable { pendingSessionData = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index c2c81894fb..7e92ff3e88 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event @@ -60,7 +59,6 @@ internal class CancelGossipRequestWorker(context: Context, @Inject lateinit var sendToDeviceTask: SendToDeviceTask @Inject lateinit var cryptoStore: IMXCryptoStore - @Inject lateinit var eventBus: EventBus @Inject lateinit var credentials: Credentials override fun injectWith(injector: SessionComponent) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index 085469e9d9..e8d567b944 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event @@ -52,7 +51,6 @@ internal class SendGossipRequestWorker(context: Context, @Inject lateinit var sendToDeviceTask: SendToDeviceTask @Inject lateinit var cryptoStore: IMXCryptoStore - @Inject lateinit var eventBus: EventBus @Inject lateinit var credentials: Credentials override fun injectWith(injector: SessionComponent) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index bcaa16f356..8c68057056 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event @@ -54,7 +53,6 @@ internal class SendGossipWorker(context: Context, @Inject lateinit var sendToDeviceTask: SendToDeviceTask @Inject lateinit var cryptoStore: IMXCryptoStore - @Inject lateinit var eventBus: EventBus @Inject lateinit var credentials: Credentials @Inject lateinit var messageEncrypter: MessageEncrypter @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt index 36b667911d..5c59cfd80e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt @@ -19,20 +19,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface CreateKeysBackupVersionTask : Task internal class DefaultCreateKeysBackupVersionTask @Inject constructor( private val roomKeysApi: RoomKeysApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : CreateKeysBackupVersionTask { override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = roomKeysApi.createKeysBackupVersion(params) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt index d174be20a2..ec09da7240 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteBackupTask : Task { @@ -30,11 +30,11 @@ internal interface DeleteBackupTask : Task { internal class DefaultDeleteBackupTask @Inject constructor( private val roomKeysApi: RoomKeysApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : DeleteBackupTask { override suspend fun execute(params: DeleteBackupTask.Params) { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = roomKeysApi.deleteBackup(params.version) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt index 6826596ba4..9c477efb78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteRoomSessionDataTask : Task { @@ -32,11 +32,11 @@ internal interface DeleteRoomSessionDataTask : Task { @@ -31,11 +31,11 @@ internal interface DeleteRoomSessionsDataTask : Task { @@ -30,11 +30,11 @@ internal interface DeleteSessionsDataTask : Task internal class DefaultGetKeysBackupLastVersionTask @Inject constructor( private val roomKeysApi: RoomKeysApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetKeysBackupLastVersionTask { override suspend fun execute(params: Unit): KeysVersionResult { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = roomKeysApi.getKeysBackupLastVersion() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt index dd2dd70e4d..13c99fb0f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt @@ -18,20 +18,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetKeysBackupVersionTask : Task internal class DefaultGetKeysBackupVersionTask @Inject constructor( private val roomKeysApi: RoomKeysApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetKeysBackupVersionTask { override suspend fun execute(params: String): KeysVersionResult { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = roomKeysApi.getKeysBackupVersion(params) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt index 8ca03491a1..168020d9cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetRoomSessionDataTask : Task { @@ -33,11 +33,11 @@ internal interface GetRoomSessionDataTask : Task { @@ -32,11 +32,11 @@ internal interface GetRoomSessionsDataTask : Task { @@ -31,11 +31,11 @@ internal interface GetSessionsDataTask : Task { @@ -35,11 +35,11 @@ internal interface StoreRoomSessionDataTask : Task { @@ -34,11 +34,11 @@ internal interface StoreRoomSessionsDataTask : Task { @@ -33,11 +33,11 @@ internal interface StoreSessionsDataTask : Task { @@ -32,11 +32,11 @@ internal interface UpdateKeysBackupVersionTask : Task { val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map) - val keysClaimResponse = executeRequest(eventBus) { + val keysClaimResponse = executeRequest(globalErrorReceiver) { apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) } val map = MXUsersDevicesMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt index e5078d5b4e..8f1569a037 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt @@ -20,9 +20,9 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteDeviceTask : Task { @@ -33,12 +33,12 @@ internal interface DeleteDeviceTask : Task { internal class DefaultDeleteDeviceTask @Inject constructor( private val cryptoApi: CryptoApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : DeleteDeviceTask { override suspend fun execute(params: DeleteDeviceTask.Params) { try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()) } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt index 38eee7f932..b4c1e6d27c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt @@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteDeviceWithUserPasswordTask : Task { @@ -37,11 +37,11 @@ internal interface DeleteDeviceWithUserPasswordTask : Task { @@ -35,7 +35,7 @@ internal interface DownloadKeysForUsersTask : Task { @@ -29,11 +29,11 @@ internal interface GetDeviceInfoTask : Task internal class DefaultGetDevicesTask @Inject constructor( private val cryptoApi: CryptoApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetDevicesTask { override suspend fun execute(params: Unit): DevicesListResponse { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = cryptoApi.getDevices() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt index 289a5226f5..4cc9ab2fcb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.rest.KeyChangesResponse +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetKeyChangesTask : Task { @@ -34,11 +34,11 @@ internal interface GetKeyChangesTask : Task { internal class DefaultRedactEventTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus) : RedactEventTask { + private val globalErrorReceiver: GlobalErrorReceiver) : RedactEventTask { override suspend fun execute(params: RedactEventTask.Params): String { - val executeRequest = executeRequest(eventBus) { + val executeRequest = executeRequest(globalErrorReceiver) { apiCall = roomAPI.redactEvent( txId = params.txID, roomId = params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index 8b739c4b64..b772bfbce2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -15,11 +15,12 @@ */ package org.matrix.android.sdk.internal.crypto.tasks -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task @@ -35,16 +36,24 @@ internal interface SendEventTask : Task { internal class DefaultSendEventTask @Inject constructor( private val localEchoRepository: LocalEchoRepository, private val encryptEventTask: DefaultEncryptEventTask, + private val loadRoomMembersTask: LoadRoomMembersTask, private val roomAPI: RoomAPI, - private val eventBus: EventBus) : SendEventTask { + private val globalErrorReceiver: GlobalErrorReceiver) : SendEventTask { override suspend fun execute(params: SendEventTask.Params): String { try { + // Make sure to load all members in the room before sending the event. + params.event.roomId + ?.takeIf { params.encrypt } + ?.let { roomId -> + loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId)) + } + val event = handleEncryption(params) val localId = event.eventId!! localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING) - val executeRequest = executeRequest(eventBus) { + val executeRequest = executeRequest(globalErrorReceiver) { apiCall = roomAPI.send( localId, roomId = event.roomId ?: "", diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt index 37e0bbc887..d2af91601b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt @@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject import kotlin.random.Random @@ -38,7 +38,7 @@ internal interface SendToDeviceTask : Task { internal class DefaultSendToDeviceTask @Inject constructor( private val cryptoApi: CryptoApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SendToDeviceTask { override suspend fun execute(params: SendToDeviceTask.Params) { @@ -46,7 +46,7 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = cryptoApi.sendToDevice( params.eventType, params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt index cedb7a6618..c39dfb1016 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -15,10 +15,10 @@ */ package org.matrix.android.sdk.internal.crypto.tasks -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository @@ -37,7 +37,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( private val encryptEventTask: DefaultEncryptEventTask, private val roomAPI: RoomAPI, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, - private val eventBus: EventBus) : SendVerificationMessageTask { + private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask { override suspend fun execute(params: SendVerificationMessageTask.Params): String { val event = handleEncryption(params) @@ -45,7 +45,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( try { localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING) - val executeRequest = executeRequest(eventBus) { + val executeRequest = executeRequest(globalErrorReceiver) { apiCall = roomAPI.send( localId, roomId = event.roomId ?: "", diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt index 51b9624554..b835d46236 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SetDeviceNameTask : Task { @@ -34,14 +34,14 @@ internal interface SetDeviceNameTask : Task { internal class DefaultSetDeviceNameTask @Inject constructor( private val cryptoApi: CryptoApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SetDeviceNameTask { override suspend fun execute(params: SetDeviceNameTask.Params) { val body = UpdateDeviceInfoBody( displayName = params.deviceName ) - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt index 6216a3a305..eb53bbbf8d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt @@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -38,7 +38,7 @@ internal interface UploadKeysTask : Task $body") - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = cryptoApi.uploadKeys(body) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt index a4e10ddbfc..c50faf37b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UploadSignaturesTask : Task { @@ -31,12 +31,12 @@ internal interface UploadSignaturesTask : Task(eventBus) { + val response = executeRequest(globalErrorReceiver) { this.isRetryable = true this.maxRetryCount = 10 this.apiCall = cryptoApi.uploadSignatures(params.signatures) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt index 038ef9dbd3..cceff355bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt @@ -25,9 +25,9 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth import org.matrix.android.sdk.internal.crypto.model.toRest +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UploadSigningKeysTask : Task { @@ -55,7 +55,7 @@ data class UploadSigningKeys(val failures: Map?) : Failure.FeatureF internal class DefaultUploadSigningKeysTask @Inject constructor( private val cryptoApi: CryptoApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : UploadSigningKeysTask { override suspend fun execute(params: UploadSigningKeysTask.Params) { @@ -87,7 +87,7 @@ internal class DefaultUploadSigningKeysTask @Inject constructor( } private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) { - val keysQueryResponse = executeRequest(eventBus) { + val keysQueryResponse = executeRequest(globalErrorReceiver) { apiCall = cryptoApi.uploadSigningKeys(uploadQuery) } if (keysQueryResponse.failures?.isNotEmpty() == true) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index b970ec60e2..57002b5a60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -21,6 +21,8 @@ import io.realm.RealmMigration import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields +import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import timber.log.Timber import javax.inject.Inject @@ -28,7 +30,7 @@ import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 6L + const val SESSION_STORE_SCHEMA_VERSION = 7L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -40,6 +42,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 3) migrateTo4(realm) if (oldVersion <= 4) migrateTo5(realm) if (oldVersion <= 5) migrateTo6(realm) + if (oldVersion <= 6) migrateTo7(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -105,4 +108,18 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { .addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java) .addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java) } + + private fun migrateTo7(realm: DynamicRealm) { + Timber.d("Step 6 -> 7") + realm.schema.get("RoomEntity") + ?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java) + ?.transform { obj -> + if (obj.getBoolean("areAllMembersLoaded")) { + obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name) + } else { + obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name) + } + } + ?.removeField("areAllMembersLoaded") + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt index 9af1646a4c..3ff2532604 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt @@ -23,8 +23,7 @@ import io.realm.annotations.PrimaryKey internal open class RoomEntity(@PrimaryKey var roomId: String = "", var chunks: RealmList = RealmList(), - var sendingTimelineEvents: RealmList = RealmList(), - var areAllMembersLoaded: Boolean = false + var sendingTimelineEvents: RealmList = RealmList() ) : RealmObject() { private var membershipStr: String = Membership.NONE.name @@ -36,5 +35,14 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "", membershipStr = value.name } + private var membersLoadStatusStr: String = RoomMembersLoadStatusType.NONE.name + var membersLoadStatus: RoomMembersLoadStatusType + get() { + return RoomMembersLoadStatusType.valueOf(membersLoadStatusStr) + } + set(value) { + membersLoadStatusStr = value.name + } + companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/eventbus/EventBusTimberLogger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt similarity index 63% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/eventbus/EventBusTimberLogger.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt index 2cbd7ba7f0..79fe17253b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/eventbus/EventBusTimberLogger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt @@ -14,18 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.eventbus +package org.matrix.android.sdk.internal.database.model -import org.greenrobot.eventbus.Logger -import timber.log.Timber -import java.util.logging.Level - -class EventBusTimberLogger : Logger { - override fun log(level: Level, msg: String) { - Timber.d(msg) - } - - override fun log(level: Level, msg: String, th: Throwable) { - Timber.e(th, msg) - } +internal enum class RoomMembersLoadStatusType { + NONE, + LOADING, + LOADED } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt new file mode 100644 index 0000000000..9afdb40ed1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt @@ -0,0 +1,54 @@ +/* + * Copyright 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. + * 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.internal.network + +import org.matrix.android.sdk.api.failure.GlobalError +import org.matrix.android.sdk.internal.auth.SessionParamsStore +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.task.TaskExecutor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@SessionScope +internal class GlobalErrorHandler @Inject constructor( + private val taskExecutor: TaskExecutor, + private val sessionParamsStore: SessionParamsStore, + @SessionId private val sessionId: String +) : GlobalErrorReceiver { + + var listener: Listener? = null + + override fun handleGlobalError(globalError: GlobalError) { + Timber.e("Global error received: $globalError") + + if (globalError is GlobalError.InvalidToken && globalError.softLogout) { + // Mark the token has invalid + taskExecutor.executorScope.launch(Dispatchers.IO) { + sessionParamsStore.setTokenInvalid(sessionId) + } + } + + listener?.onGlobalError(globalError) + } + + internal interface Listener { + fun onGlobalError(globalError: GlobalError) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorReceiver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorReceiver.kt new file mode 100644 index 0000000000..47607ba893 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorReceiver.kt @@ -0,0 +1,23 @@ +/* + * Copyright 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. + * 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.internal.network + +import org.matrix.android.sdk.api.failure.GlobalError + +internal interface GlobalErrorReceiver { + fun handleGlobalError(globalError: GlobalError) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index 2535a5347a..442029127d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.network import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.internal.network.ssl.CertUtil @@ -27,10 +26,10 @@ import retrofit2.awaitResponse import timber.log.Timber import java.io.IOException -internal suspend inline fun executeRequest(eventBus: EventBus?, - block: Request.() -> Unit) = Request(eventBus).apply(block).execute() +internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + block: Request.() -> Unit) = Request(globalErrorReceiver).apply(block).execute() -internal class Request(private val eventBus: EventBus?) { +internal class Request(private val globalErrorReceiver: GlobalErrorReceiver?) { var isRetryable = false var initialDelay: Long = 100L @@ -47,7 +46,7 @@ internal class Request(private val eventBus: EventBus?) { response.body() ?: throw IllegalStateException("The request returned a null body") } else { - throw response.toFailure(eventBus) + throw response.toFailure(globalErrorReceiver) } } catch (exception: Throwable) { // Log some details about the request which has failed diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt index c54af571d8..dd5a69dd3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.internal.di.MoshiProvider import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.ResponseBody -import org.greenrobot.eventbus.EventBus import retrofit2.Response import timber.log.Timber import java.io.IOException @@ -54,18 +53,18 @@ internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response { /** * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ -internal fun Response.toFailure(eventBus: EventBus?): Failure { - return toFailure(errorBody(), code(), eventBus) +internal fun Response.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure { + return toFailure(errorBody(), code(), globalErrorReceiver) } /** * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ -internal fun okhttp3.Response.toFailure(eventBus: EventBus?): Failure { - return toFailure(body, code, eventBus) +internal fun okhttp3.Response.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure { + return toFailure(body, code, globalErrorReceiver) } -private fun toFailure(errorBody: ResponseBody?, httpCode: Int, eventBus: EventBus?): Failure { +private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiver: GlobalErrorReceiver?): Failure { if (errorBody == null) { return Failure.Unknown(RuntimeException("errorBody should not be null")) } @@ -79,12 +78,12 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int, eventBus: EventBu if (matrixError != null) { if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) { - // Also send this error to the bus, for a global management - eventBus?.post(GlobalError.ConsentNotGivenError(matrixError.consentUri)) + // Also send this error to the globalErrorReceiver, for a global management + globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri)) } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */ && matrixError.code == MatrixError.M_UNKNOWN_TOKEN) { - // Also send this error to the bus, for a global management - eventBus?.post(GlobalError.InvalidToken(matrixError.isSoftLogout)) + // Also send this error to the globalErrorReceiver, for a global management + globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout)) } return Failure.ServerError(matrixError, httpCode) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 3bc66f8f88..4dca3dc4b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -19,12 +19,7 @@ package org.matrix.android.sdk.internal.session import androidx.annotation.MainThread import dagger.Lazy import io.realm.RealmConfiguration -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import okhttp3.OkHttpClient -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError @@ -66,13 +61,12 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.WorkManagerProvider +import org.matrix.android.sdk.internal.network.GlobalErrorHandler import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.session.sync.job.SyncThread import org.matrix.android.sdk.internal.session.sync.job.SyncWorker -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.createUIHandler import timber.log.Timber import javax.inject.Inject @@ -82,7 +76,7 @@ import javax.inject.Provider internal class DefaultSession @Inject constructor( override val sessionParams: SessionParams, private val workManagerProvider: WorkManagerProvider, - private val eventBus: EventBus, + private val globalErrorHandler: GlobalErrorHandler, @SessionId override val sessionId: String, @SessionDatabase private val realmConfiguration: RealmConfiguration, @@ -118,11 +112,9 @@ internal class DefaultSession @Inject constructor( private val accountDataService: Lazy, private val _sharedSecretStorageService: Lazy, private val accountService: Lazy, - private val coroutineDispatchers: MatrixCoroutineDispatchers, private val defaultIdentityService: DefaultIdentityService, private val integrationManagerService: IntegrationManagerService, private val thirdPartyService: ThirdPartyService, - private val taskExecutor: TaskExecutor, private val callSignalingService: Lazy, @UnauthenticatedWithCertificate private val unauthenticatedWithCertificateOkHttpClient: Lazy, @@ -143,7 +135,8 @@ internal class DefaultSession @Inject constructor( ProfileService by profileService.get(), AccountDataService by accountDataService.get(), AccountService by accountService.get(), - ThirdPartyService by thirdPartyService { + ThirdPartyService by thirdPartyService, + GlobalErrorHandler.Listener { override val sharedSecretStorageService: SharedSecretStorageService get() = _sharedSecretStorageService.get() @@ -165,7 +158,7 @@ internal class DefaultSession @Inject constructor( uiHandler.post { lifecycleObservers.forEach { it.onStart() } } - eventBus.register(this) + globalErrorHandler.listener = this eventSenderProcessor.start() } @@ -209,7 +202,7 @@ internal class DefaultSession @Inject constructor( } cryptoService.get().close() isOpen = false - eventBus.unregister(this) + globalErrorHandler.listener = null eventSenderProcessor.interrupt() } @@ -237,16 +230,7 @@ internal class DefaultSession @Inject constructor( workManagerProvider.cancelAllWorks() } - @Subscribe(threadMode = ThreadMode.MAIN) - fun onGlobalError(globalError: GlobalError) { - if (globalError is GlobalError.InvalidToken - && globalError.softLogout) { - // Mark the token has invalid - taskExecutor.executorScope.launch(Dispatchers.IO) { - sessionParamsStore.setTokenInvalid(sessionId) - } - } - + override fun onGlobalError(globalError: GlobalError) { sessionListeners.dispatchGlobalError(globalError) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 96b44917bd..468c193ad3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -26,7 +26,6 @@ import dagger.Provides import dagger.multibindings.IntoSet import io.realm.RealmConfiguration import okhttp3.OkHttpClient -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -61,9 +60,10 @@ import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserMd5 -import org.matrix.android.sdk.internal.eventbus.EventBusTimberLogger import org.matrix.android.sdk.internal.network.DefaultNetworkConnectivityChecker import org.matrix.android.sdk.internal.network.FallbackNetworkCallbackStrategy +import org.matrix.android.sdk.internal.network.GlobalErrorHandler +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.NetworkCallbackStrategy import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker import org.matrix.android.sdk.internal.network.PreferredNetworkCallbackStrategy @@ -256,16 +256,6 @@ internal abstract class SessionModule { .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString()) } - @JvmStatic - @Provides - @SessionScope - fun providesEventBus(): EventBus { - return EventBus - .builder() - .logger(EventBusTimberLogger()) - .build() - } - @JvmStatic @Provides @SessionScope @@ -294,6 +284,9 @@ internal abstract class SessionModule { @Binds abstract fun bindSession(session: DefaultSession): Session + @Binds + abstract fun bindGlobalErrorReceiver(handler: GlobalErrorHandler): GlobalErrorReceiver + @Binds abstract fun bindNetworkConnectivityChecker(checker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt index 869d3f516c..1f043b0a9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.account import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface ChangePasswordTask : Task { @@ -32,14 +32,14 @@ internal interface ChangePasswordTask : Task { internal class DefaultChangePasswordTask @Inject constructor( private val accountAPI: AccountAPI, - private val eventBus: EventBus, + private val globalErrorReceiver: GlobalErrorReceiver, @UserId private val userId: String ) : ChangePasswordTask { override suspend fun execute(params: ChangePasswordTask.Params) { val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword) try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = accountAPI.changePassword(changePasswordParams) } } catch (throwable: Throwable) { @@ -49,7 +49,7 @@ internal class DefaultChangePasswordTask @Inject constructor( /* Avoid infinite loop */ && changePasswordParams.auth?.session == null) { // Retry with authentication - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = accountAPI.changePassword( changePasswordParams.copy(auth = changePasswordParams.auth?.copy(session = registrationFlowResponse.session)) ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt index ac5febcdae..9fb1cbb7d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.internal.session.account import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.cleanup.CleanupSession import org.matrix.android.sdk.internal.session.identity.IdentityDisconnectTask import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -34,7 +34,7 @@ internal interface DeactivateAccountTask : Task(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = accountAPI.deactivate(deactivateAccountParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index a6de65f9f5..4576675d4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel 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.CallCapabilities 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 @@ -185,6 +186,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa call.apply { opponentPartyId = Optional.from(content.partyId) opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION + capabilities = content.capabilities ?: CallCapabilities() } callListenersDispatcher.onCallAnswerReceived(content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt index 1cedee3374..b21ec1113a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.call import org.matrix.android.sdk.api.session.call.TurnServerResponse +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal abstract class GetTurnServerTask : Task { @@ -27,10 +27,10 @@ internal abstract class GetTurnServerTask : Task? = null override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION + override var capabilities: CallCapabilities? = null override var state: CallState = CallState.Idle set(value) { @@ -98,7 +107,8 @@ internal class MxCallImpl( partyId = ourPartyId, lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS, offer = CallInviteContent.Offer(sdp = sdpString), - version = MxCall.VOIP_PROTO_VERSION.toString() + version = MxCall.VOIP_PROTO_VERSION.toString(), + capabilities = buildCapabilities() ) .let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } @@ -158,7 +168,8 @@ internal class MxCallImpl( callId = callId, partyId = ourPartyId, answer = CallAnswerContent.Answer(sdp = sdpString), - version = MxCall.VOIP_PROTO_VERSION.toString() + version = MxCall.VOIP_PROTO_VERSION.toString(), + capabilities = buildCapabilities() ) .let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } @@ -191,6 +202,31 @@ internal class MxCallImpl( .also { eventSenderProcessor.postEvent(it) } } + override suspend fun transfer(targetUserId: String, targetRoomId: String?) { + val profileInfoParams = GetProfileInfoTask.Params(targetUserId) + val profileInfo = try { + getProfileInfoTask.execute(profileInfoParams) + } catch (failure: Throwable) { + Timber.v("Fail fetching profile info of $targetUserId while transferring call") + null + } + CallReplacesContent( + callId = callId, + partyId = ourPartyId, + replacementId = UUID.randomUUID().toString(), + version = MxCall.VOIP_PROTO_VERSION.toString(), + targetUser = CallReplacesContent.TargetUser( + id = targetUserId, + displayName = profileInfo?.get(ProfileService.DISPLAY_NAME_KEY) as? String, + avatarUrl = profileInfo?.get(ProfileService.AVATAR_URL_KEY) as? String + ), + targerRoomId = targetRoomId, + createCall = UUID.randomUUID().toString() + ) + .let { createEventAndLocalEcho(type = EventType.CALL_REPLACES, roomId = roomId, content = it.toContent()) } + .also { eventSenderProcessor.postEvent(it) } + } + private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event { return Event( roomId = roomId, @@ -203,4 +239,12 @@ internal class MxCallImpl( ) .also { localEchoEventFactory.createLocalEcho(it) } } + + private fun buildCapabilities(): CallCapabilities? { + return if (matrixConfiguration.supportsCallTransfer) { + CallCapabilities(true) + } else { + null + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt index 4fc1d67c05..8fa595db30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt @@ -30,10 +30,10 @@ import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import okio.BufferedSink import okio.source -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.internal.di.Authenticated +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.ProgressRequestBody import org.matrix.android.sdk.internal.network.awaitResponse import org.matrix.android.sdk.internal.network.toFailure @@ -45,7 +45,7 @@ import javax.inject.Inject internal class FileUploader @Inject constructor(@Authenticated private val okHttpClient: OkHttpClient, - private val eventBus: EventBus, + private val globalErrorReceiver: GlobalErrorReceiver, private val context: Context, contentUrlResolver: ContentUrlResolver, moshi: Moshi) { @@ -115,7 +115,7 @@ internal class FileUploader @Inject constructor(@Authenticated return okHttpClient.newCall(request).awaitResponse().use { response -> if (!response.isSuccessful) { - throw response.toFailure(eventBus) + throw response.toFailure(globalErrorReceiver) } else { response.body?.source()?.let { responseAdapter.fromJson(it) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt index 69ced92fe5..da747934e2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.filter import org.matrix.android.sdk.api.session.sync.FilterService import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject /** @@ -37,7 +37,7 @@ internal class DefaultSaveFilterTask @Inject constructor( @UserId private val userId: String, private val filterAPI: FilterApi, private val filterRepository: FilterRepository, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SaveFilterTask { override suspend fun execute(params: SaveFilterTask.Params) { @@ -59,7 +59,7 @@ internal class DefaultSaveFilterTask @Inject constructor( } val updated = filterRepository.storeFilter(filterBody, roomFilter) if (updated) { - val filterResponse = executeRequest(eventBus) { + val filterResponse = executeRequest(globalErrorReceiver) { // TODO auto retry apiCall = filterAPI.uploadFilter(userId, filterBody) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt index dd703a5e93..9836164aec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt @@ -23,13 +23,13 @@ import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.group.model.GroupRooms import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse import org.matrix.android.sdk.internal.session.group.model.GroupUsers import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction -import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -43,7 +43,7 @@ internal interface GetGroupDataTask : Task { internal class DefaultGetGroupDataTask @Inject constructor( private val groupAPI: GroupAPI, @SessionDatabase private val monarchy: Monarchy, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetGroupDataTask { private data class GroupData( @@ -64,13 +64,13 @@ internal class DefaultGetGroupDataTask @Inject constructor( } Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}") val data = groupIds.map { groupId -> - val groupSummary = executeRequest(eventBus) { + val groupSummary = executeRequest(globalErrorReceiver) { apiCall = groupAPI.getSummary(groupId) } - val groupRooms = executeRequest(eventBus) { + val groupRooms = executeRequest(globalErrorReceiver) { apiCall = groupAPI.getRooms(groupId) } - val groupUsers = executeRequest(eventBus) { + val groupUsers = executeRequest(globalErrorReceiver) { apiCall = groupAPI.getUsers(groupId) } GroupData(groupId, groupSummary, groupRooms, groupUsers) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index f3686b02d3..845cfb392e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.homeserver import com.zhuinden.monarchy.Monarchy -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities @@ -27,6 +26,7 @@ import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEnti import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerConfigExtractor import org.matrix.android.sdk.internal.session.media.GetMediaConfigResult @@ -44,7 +44,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( private val capabilitiesAPI: CapabilitiesAPI, private val mediaAPI: MediaAPI, @SessionDatabase private val monarchy: Monarchy, - private val eventBus: EventBus, + private val globalErrorReceiver: GlobalErrorReceiver, private val getWellknownTask: GetWellknownTask, private val configExtractor: IntegrationManagerConfigExtractor, private val homeServerConnectionConfig: HomeServerConnectionConfig, @@ -65,13 +65,13 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( } val capabilities = runCatching { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = capabilitiesAPI.getCapabilities() } }.getOrNull() val mediaConfig = runCatching { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = mediaAPI.getMediaConfig() } }.getOrNull() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt index 69cdfa8faa..a218f3f93c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.media import com.zhuinden.monarchy.Monarchy -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.session.media.PreviewUrlData import org.matrix.android.sdk.api.util.JsonDict @@ -25,6 +24,7 @@ import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction @@ -41,7 +41,7 @@ internal interface GetPreviewUrlTask : Task(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = mediaAPI.getPreviewUrlData(url, timestamp) } .toPreviewUrlData(url) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt index 6c5dad2422..32305cd4e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.session.media -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -31,11 +31,11 @@ internal interface GetRawPreviewUrlTask : Task @@ -27,10 +27,10 @@ internal interface GetOpenIdTokenTask : Task internal class DefaultGetOpenIdTokenTask @Inject constructor( @UserId private val userId: String, private val openIdAPI: OpenIdAPI, - private val eventBus: EventBus) : GetOpenIdTokenTask { + private val globalErrorReceiver: GlobalErrorReceiver) : GetOpenIdTokenTask { override suspend fun execute(params: Unit): RequestOpenIdTokenResponse { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = openIdAPI.openIdToken(userId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt index 2e8a0b3884..6d6d70bb0d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.profile import com.google.i18n.phonenumbers.PhoneNumberUtil import com.zhuinden.monarchy.Monarchy -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction @@ -37,7 +37,7 @@ internal class DefaultAddThreePidTask @Inject constructor( private val profileAPI: ProfileAPI, @SessionDatabase private val monarchy: Monarchy, private val pendingThreePidMapper: PendingThreePidMapper, - private val eventBus: EventBus) : AddThreePidTask() { + private val globalErrorReceiver: GlobalErrorReceiver) : AddThreePidTask() { override suspend fun execute(params: Params) { when (params.threePid) { @@ -50,7 +50,7 @@ internal class DefaultAddThreePidTask @Inject constructor( val clientSecret = UUID.randomUUID().toString() val sendAttempt = 1 - val result = executeRequest(eventBus) { + val result = executeRequest(globalErrorReceiver) { val body = AddEmailBody( clientSecret = clientSecret, email = threePid.email, @@ -84,7 +84,7 @@ internal class DefaultAddThreePidTask @Inject constructor( val countryCode = parsedNumber.countryCode val country = phoneNumberUtil.getRegionCodeForCountryCode(countryCode) - val result = executeRequest(eventBus) { + val result = executeRequest(globalErrorReceiver) { val body = AddMsisdnBody( clientSecret = clientSecret, country = country, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt index dbe6bff508..a37e5380bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt @@ -19,12 +19,12 @@ package org.matrix.android.sdk.internal.session.profile import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.internal.di.AuthenticatedIdentity +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.identity.data.getIdentityServerUrlWithoutProtocol import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal abstract class BindThreePidsTask : Task { @@ -37,13 +37,13 @@ internal class DefaultBindThreePidsTask @Inject constructor(private val profileA private val identityStore: IdentityStore, @AuthenticatedIdentity private val accessTokenProvider: AccessTokenProvider, - private val eventBus: EventBus) : BindThreePidsTask() { + private val globalErrorReceiver: GlobalErrorReceiver) : BindThreePidsTask() { override suspend fun execute(params: Params) { val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = profileAPI.bindThreePid( BindThreePidBody( clientSecret = identityPendingBinding.clientSecret, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt index 3f43cbe599..3549f3613f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.session.profile -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.toMedium +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -31,10 +31,10 @@ internal abstract class DeleteThreePidTask : Task(eventBus) { + executeRequest(globalErrorReceiver) { val body = DeleteThreePidBody( medium = params.threePid.toMedium(), address = params.threePid.value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt index 0b1bf88280..1e3a2cb501 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.profile import com.zhuinden.monarchy.Monarchy -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse import org.matrix.android.sdk.api.session.identity.ThreePid @@ -26,6 +25,7 @@ import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction @@ -45,7 +45,7 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val pendingThreePidMapper: PendingThreePidMapper, @UserId private val userId: String, - private val eventBus: EventBus) : FinalizeAddingThreePidTask() { + private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() { override suspend fun execute(params: Params) { if (params.userWantsToCancel.not()) { @@ -58,7 +58,7 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor( ?: throw IllegalArgumentException("unknown threepid") try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { val body = FinalizeAddThreePidBody( clientSecret = pendingThreePids.clientSecret, sid = pendingThreePids.sid, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt index 5f1f621ddb..ed60c4a368 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.profile import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal abstract class GetProfileInfoTask : Task { @@ -30,10 +30,10 @@ internal abstract class GetProfileInfoTask : Task internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI, @SessionDatabase private val monarchy: Monarchy, - private val eventBus: EventBus) : RefreshUserThreePidsTask() { + private val globalErrorReceiver: GlobalErrorReceiver) : RefreshUserThreePidsTask() { override suspend fun execute(params: Unit) { - val accountThreePidsResponse = executeRequest(eventBus) { + val accountThreePidsResponse = executeRequest(globalErrorReceiver) { apiCall = profileAPI.getThreePIDs() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt index 4b863c2098..b29153d665 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.session.profile +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal abstract class SetAvatarUrlTask : Task { @@ -30,10 +30,10 @@ internal abstract class SetAvatarUrlTask : Task { internal class DefaultSetAvatarUrlTask @Inject constructor( private val profileAPI: ProfileAPI, - private val eventBus: EventBus) : SetAvatarUrlTask() { + private val globalErrorReceiver: GlobalErrorReceiver) : SetAvatarUrlTask() { override suspend fun execute(params: Params) { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { val body = SetAvatarUrlBody( avatarUrl = params.newAvatarUrl ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt index 1fa84f98c1..3f236bc589 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.session.profile +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal abstract class SetDisplayNameTask : Task { @@ -30,10 +30,10 @@ internal abstract class SetDisplayNameTask : Task { @@ -34,12 +34,12 @@ internal abstract class UnbindThreePidsTask : Task(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = profileAPI.unbindThreePid( UnbindThreePidBody( identityServerUrlWithoutProtocol, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt index 36804e06fe..efb6c6e836 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt @@ -17,13 +17,13 @@ package org.matrix.android.sdk.internal.session.profile import com.zhuinden.monarchy.Monarchy -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -40,7 +40,7 @@ internal class DefaultValidateSmsCodeTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val pendingThreePidMapper: PendingThreePidMapper, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : ValidateSmsCodeTask { override suspend fun execute(params: ValidateSmsCodeTask.Params) { @@ -58,7 +58,7 @@ internal class DefaultValidateSmsCodeTask @Inject constructor( sid = pendingThreePids.sid, code = params.code ) - val result = executeRequest(eventBus) { + val result = executeRequest(globalErrorReceiver) { apiCall = profileAPI.validateMsisdn(url, body) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt index 31c5cda5ec..d0f7cbfca3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt @@ -19,13 +19,13 @@ import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import com.zhuinden.monarchy.Monarchy -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.util.awaitTransaction @@ -45,7 +45,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) @Inject lateinit var pushersAPI: PushersAPI @Inject @SessionDatabase lateinit var monarchy: Monarchy - @Inject lateinit var eventBus: EventBus + @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver override fun injectWith(injector: SessionComponent) { injector.inject(this) @@ -81,7 +81,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) } private suspend fun setPusher(pusher: JsonPusher) { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = pushersAPI.setPusher(pusher) } monarchy.awaitTransaction { realm -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt index 28ac5db52e..03748b1528 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface AddPushRuleTask : Task { @@ -31,11 +31,11 @@ internal interface AddPushRuleTask : Task { internal class DefaultAddPushRuleTask @Inject constructor( private val pushRulesApi: PushRulesApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : AddPushRuleTask { override suspend fun execute(params: AddPushRuleTask.Params) { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt index 0c532cedbc..9fb2d51664 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPushRulesTask : Task { @@ -31,11 +31,11 @@ internal interface GetPushRulesTask : Task { internal class DefaultGetPushRulesTask @Inject constructor( private val pushRulesApi: PushRulesApi, private val savePushRulesTask: SavePushRulesTask, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetPushRulesTask { override suspend fun execute(params: GetPushRulesTask.Params) { - val response = executeRequest(eventBus) { + val response = executeRequest(globalErrorReceiver) { apiCall = pushRulesApi.getAllRules() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt index 39e970f4a8..4c7d370446 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt @@ -20,10 +20,10 @@ import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPushersTask : Task @@ -31,11 +31,11 @@ internal interface GetPushersTask : Task internal class DefaultGetPushersTask @Inject constructor( private val pushersAPI: PushersAPI, @SessionDatabase private val monarchy: Monarchy, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetPushersTask { override suspend fun execute(params: Unit) { - val response = executeRequest(eventBus) { + val response = executeRequest(globalErrorReceiver) { apiCall = pushersAPI.getPushers() } monarchy.awaitTransaction { realm -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt index 2fc97cf023..ff3122f566 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface RemovePushRuleTask : Task { @@ -31,11 +31,11 @@ internal interface RemovePushRuleTask : Task { internal class DefaultRemovePushRuleTask @Inject constructor( private val pushRulesApi: PushRulesApi, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : RemovePushRuleTask { override suspend fun execute(params: RemovePushRuleTask.Params) { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt index 1f10863799..e3f4fdb789 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import io.realm.Realm -import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import javax.inject.Inject internal interface RemovePusherTask : Task { @@ -37,7 +37,7 @@ internal interface RemovePusherTask : Task { internal class DefaultRemovePusherTask @Inject constructor( private val pushersAPI: PushersAPI, @SessionDatabase private val monarchy: Monarchy, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : RemovePusherTask { override suspend fun execute(params: RemovePusherTask.Params) { @@ -62,7 +62,7 @@ internal class DefaultRemovePusherTask @Inject constructor( data = JsonPusherData(existing.data.url, existing.data.format), append = false ) - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = pushersAPI.setPusher(deleteBody) } monarchy.awaitTransaction { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt index c2dca8a9a5..a5c220e662 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdatePushRuleActionsTask : Task { @@ -32,13 +32,13 @@ internal interface UpdatePushRuleActionsTask : Task(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled) } } @@ -47,7 +47,7 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( // Also ensure the actions are up to date val body = mapOf("actions" to params.newPushRule.actions) - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt index 4100071c90..f36b5c55fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdatePushRuleEnableStatusTask : Task { @@ -30,11 +30,11 @@ internal interface UpdatePushRuleEnableStatusTask : Task(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = directoryAPI.addRoomAlias( roomAlias = params.aliasLocalPart.toFullLocalAlias(userId), body = AddRoomAliasBody( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt index 3400fd994c..6ad3db90a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.session.room.alias -import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import org.matrix.android.sdk.internal.task.Task @@ -30,11 +30,11 @@ internal interface DeleteRoomAliasTask : Task internal class DefaultDeleteRoomAliasTask @Inject constructor( private val directoryAPI: DirectoryAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : DeleteRoomAliasTask { override suspend fun execute(params: DeleteRoomAliasTask.Params) { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = directoryAPI.deleteRoomAlias( roomAlias = params.roomAlias ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index 543d605707..a53ffc4fcd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -18,12 +18,12 @@ package org.matrix.android.sdk.internal.session.room.alias import com.zhuinden.monarchy.Monarchy import io.realm.Realm -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.findByAlias import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import org.matrix.android.sdk.internal.task.Task @@ -39,7 +39,7 @@ internal interface GetRoomIdByAliasTask : Task { @@ -52,7 +52,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor( Optional.from(null) } else { val description = tryOrNull("## Failed to get roomId from alias") { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt index 7cfce4ecdc..202cb1f6de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.session.room.alias -import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task @@ -30,12 +30,12 @@ internal interface GetRoomLocalAliasesTask : Task { // We do not check for "org.matrix.msc2432", so the API may be missing - val response = executeRequest(eventBus) { + val response = executeRequest(globalErrorReceiver) { apiCall = roomAPI.getAliases(roomId = params.roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index 25ba493891..51a849a35e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.session.room.alias -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import javax.inject.Inject @@ -27,7 +27,7 @@ import javax.inject.Inject internal class RoomAliasAvailabilityChecker @Inject constructor( @UserId private val userId: String, private val directoryAPI: DirectoryAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) { /** * @param aliasLocalPart the local part of the alias. @@ -41,7 +41,7 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( // Check alias availability val fullAlias = aliasLocalPart.toFullLocalAlias(userId) try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = directoryAPI.getRoomIdByAlias(fullAlias) } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index ef792ab98e..9c16bd1b0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.session.room.alias.RoomAliasError @@ -32,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker @@ -55,7 +55,7 @@ internal class DefaultCreateRoomTask @Inject constructor( @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { @@ -75,7 +75,7 @@ internal class DefaultCreateRoomTask @Inject constructor( val createRoomBody = createRoomBodyBuilder.build(params) val createRoomResponse = try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.createRoom(createRoomBody) } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt index f2bd0c5f69..edd8ae9b0d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt @@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.session.room.directory import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPublicRoomTask : Task { @@ -33,11 +33,11 @@ internal interface GetPublicRoomTask : Task(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId) } .visibility diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt index 33b12aa1ca..cbb0b6d5d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.session.room.directory -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson @@ -33,11 +33,11 @@ internal interface SetRoomDirectoryVisibilityTask : Task(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = directoryAPI.setRoomDirectoryVisibility( params.roomId, RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 627f927ad8..2be90bf8e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -17,25 +17,30 @@ package org.matrix.android.sdk.internal.session.room.membership import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.kotlin.createObject +import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction -import io.realm.Realm -import io.realm.kotlin.createObject -import org.greenrobot.eventbus.EventBus +import java.util.concurrent.TimeUnit import javax.inject.Inject internal interface LoadRoomMembersTask : Task { @@ -52,17 +57,44 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( private val syncTokenStore: SyncTokenStore, private val roomSummaryUpdater: RoomSummaryUpdater, private val roomMemberEventHandler: RoomMemberEventHandler, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { - if (areAllMembersAlreadyLoaded(params.roomId)) { - return + when (getRoomMembersLoadStatus(params.roomId)) { + RoomMembersLoadStatusType.NONE -> doRequest(params) + RoomMembersLoadStatusType.LOADING -> waitPreviousRequestToFinish(params) + RoomMembersLoadStatusType.LOADED -> Unit } + } + + private suspend fun waitPreviousRequestToFinish(params: LoadRoomMembersTask.Params) { + try { + awaitNotEmptyResult(monarchy.realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomEntity::class.java) + .equalTo(RoomEntityFields.ROOM_ID, params.roomId) + .equalTo(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, RoomMembersLoadStatusType.LOADED.name) + } + } catch (exception: TimeoutCancellationException) { + // Timeout, do the request anyway (?) + doRequest(params) + } + } + + private suspend fun doRequest(params: LoadRoomMembersTask.Params) { + setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.LOADING) + val lastToken = syncTokenStore.getLastToken() - val response = executeRequest(eventBus) { - apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) + val response = try { + executeRequest(globalErrorReceiver) { + apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) + } + } catch (throwable: Throwable) { + // Revert status to NONE + setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.NONE) + throw throwable } + // This will also set the status to LOADED insertInDb(response, params.roomId) } @@ -84,14 +116,23 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } roomMemberEventHandler.handle(realm, roomId, roomMemberEvent) } - roomEntity.areAllMembersLoaded = true + roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED roomSummaryUpdater.update(realm, roomId, updateMembers = true) } } - private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { - return Realm.getInstance(monarchy.realmConfiguration).use { - RoomEntity.where(it, roomId).findFirst()?.areAllMembersLoaded ?: false + private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType { + var result: RoomMembersLoadStatusType? + Realm.getInstance(monarchy.realmConfiguration).use { + result = RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus + } + return result ?: RoomMembersLoadStatusType.NONE + } + + private suspend fun setRoomMembersLoadStatus(roomId: String, status: RoomMembersLoadStatusType) { + monarchy.awaitTransaction { realm -> + val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) + roomEntity.membersLoadStatus = status } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt index 854a332679..05503bd643 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.session.room.membership.joining +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface InviteTask : Task { @@ -32,11 +32,11 @@ internal interface InviteTask : Task { internal class DefaultInviteTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : InviteTask { override suspend fun execute(params: InviteTask.Params) { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { val body = InviteBody(params.userId, params.reason) apiCall = roomAPI.invite(params.roomId, body) isRetryable = true 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 dd1dc5fa8a..140c6841f5 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 @@ -30,7 +30,7 @@ import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.task.Task import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException -import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -48,13 +48,13 @@ internal class DefaultJoinRoomTask @Inject constructor( @SessionDatabase private val realmConfiguration: RealmConfiguration, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.join(params.roomIdOrAlias, params.viaServers, mapOf("reason" to params.reason)) } } catch (failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt index 58e34a15ec..37bb7570d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -21,13 +21,13 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -40,7 +40,7 @@ internal interface LeaveRoomTask : Task { internal class DefaultLeaveRoomTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus, + private val globalErrorReceiver: GlobalErrorReceiver, private val stateEventDataSource: StateEventDataSource, private val roomSummaryDataSource: RoomSummaryDataSource, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource @@ -68,7 +68,7 @@ internal class DefaultLeaveRoomTask @Inject constructor( leaveRoom(predecessorRoomId, reason) } try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) } } catch (failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt index 80af00fc78..d237ec795e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt @@ -16,11 +16,11 @@ package org.matrix.android.sdk.internal.session.room.membership.threepid -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.toMedium import org.matrix.android.sdk.internal.di.AuthenticatedIdentity +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask @@ -39,7 +39,7 @@ internal interface InviteThreePidTask : Task { internal class DefaultInviteThreePidTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus, + private val globalErrorReceiver: GlobalErrorReceiver, private val identityStore: IdentityStore, private val ensureIdentityTokenTask: EnsureIdentityTokenTask, @AuthenticatedIdentity @@ -52,7 +52,7 @@ internal class DefaultInviteThreePidTask @Inject constructor( val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { val body = ThreePidInviteBody( idServer = identityServerUrlWithoutProtocol, idAccessToken = identityServerAccessToken, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt index 03ea2408f0..dbec6b555c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.session.room.peeking -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task @@ -31,11 +31,11 @@ internal interface ResolveRoomStateTask : Task { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = roomAPI.getRoomState(params.roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index a98bb02c83..c7f962a699 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import io.realm.Realm -import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import timber.log.Timber import javax.inject.Inject import kotlin.collections.set @@ -58,7 +58,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( private val roomFullyReadHandler: RoomFullyReadHandler, private val readReceiptHandler: ReadReceiptHandler, @UserId private val userId: String, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SetReadMarkersTask { override suspend fun execute(params: SetReadMarkersTask.Params) { @@ -96,7 +96,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( updateDatabase(params.roomId, markers, shouldUpdateRoomSummary) } if (markers.isNotEmpty()) { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { isRetryable = true apiCall = roomAPI.sendReadMarker(params.roomId, markers) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt index 51eecb8c2a..99d02b50da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt @@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.session.room.relation import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface FetchEditHistoryTask : Task> { @@ -35,11 +35,11 @@ internal interface FetchEditHistoryTask : Task { - val response = executeRequest(eventBus) { + val response = executeRequest(globalErrorReceiver) { apiCall = roomAPI.getRelations(params.roomId, params.eventId, RelationType.REPLACE, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt index 25dfe32cbb..c12597bea0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt @@ -18,12 +18,12 @@ package org.matrix.android.sdk.internal.session.room.relation import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.RoomAPI @@ -47,7 +47,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI - @Inject lateinit var eventBus: EventBus + @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver @Inject lateinit var localEchoRepository: LocalEchoRepository override fun injectWith(injector: SessionComponent) { @@ -84,7 +84,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) } private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.sendRelation( roomId = roomId, parentId = relatedEventId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt index bd11937676..9c6e9907a4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.session.room.reporting +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface ReportContentTask : Task { @@ -33,11 +33,11 @@ internal interface ReportContentTask : Task { internal class DefaultReportContentTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : ReportContentTask { override suspend fun execute(params: ReportContentTask.Params) { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt index f4871ab35d..f742271fa7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.room.send import com.zhuinden.monarchy.Monarchy import io.realm.Realm -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -42,7 +41,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater -import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline +import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber @@ -52,7 +51,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private private val taskExecutor: TaskExecutor, private val realmSessionProvider: RealmSessionProvider, private val roomSummaryUpdater: RoomSummaryUpdater, - private val eventBus: EventBus, + private val timelineInput: TimelineInput, private val timelineEventMapper: TimelineEventMapper) { fun createLocalEcho(event: Event) { @@ -76,7 +75,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } } val timelineEvent = timelineEventMapper.map(timelineEventEntity) - eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent)) + timelineInput.onLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent) taskExecutor.executorScope.asyncTransaction(monarchy) { realm -> val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply { this.insertType = EventInsertType.LOCAL_ECHO @@ -90,7 +89,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private fun updateSendState(eventId: String, roomId: String?, sendState: SendState) { Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}") - eventBus.post(DefaultTimeline.OnLocalEchoUpdated(roomId ?: "", eventId, sendState)) + timelineInput.onLocalEchoUpdated(roomId = roomId ?: "", eventId = eventId, sendState = sendState) updateEchoAsync(eventId) { realm, sendingEventEntity -> if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) { // If already synced, do not put as sent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index 682865eaee..c901c7e18e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt @@ -18,8 +18,8 @@ package org.matrix.android.sdk.internal.session.room.send import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.RoomAPI @@ -46,7 +46,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI - @Inject lateinit var eventBus: EventBus + @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver override fun injectWith(injector: SessionComponent) { injector.inject(this) @@ -55,7 +55,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) override suspend fun doSafeWork(params: Params): Result { val eventId = params.eventId return runCatching { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.redactEvent( params.txID, params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt index 37a429d242..c1fc2fd9fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt @@ -20,7 +20,6 @@ import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import io.realm.RealmConfiguration -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.send.SendState @@ -54,7 +53,6 @@ internal class SendEventWorker(context: Context, @Inject lateinit var localEchoRepository: LocalEchoRepository @Inject lateinit var sendEventTask: SendEventTask @Inject lateinit var cryptoService: CryptoService - @Inject lateinit var eventBus: EventBus @Inject lateinit var cancelSendTracker: CancelSendTracker @SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt index dfbac347d9..a6836c8086 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt @@ -49,8 +49,10 @@ internal class QueueMemento @Inject constructor(context: Context, } fun unTrack(task: QueuedTask) { - managedTaskInfos.remove(task) - persist() + synchronized(managedTaskInfos) { + managedTaskInfos.remove(task) + persist() + } } private fun persist() { @@ -64,19 +66,17 @@ internal class QueueMemento @Inject constructor(context: Context, } private fun toTaskInfo(task: QueuedTask, order: Int): TaskInfo? { - synchronized(managedTaskInfos) { - return when (task) { - is SendEventQueuedTask -> SendEventTaskInfo( - localEchoId = task.event.eventId ?: "", - encrypt = task.encrypt, - order = order - ) - is RedactQueuedTask -> RedactEventTaskInfo( - redactionLocalEcho = task.redactionLocalEchoId, - order = order - ) - else -> null - } + return when (task) { + is SendEventQueuedTask -> SendEventTaskInfo( + localEchoId = task.event.eventId ?: "", + encrypt = task.encrypt, + order = order + ) + is RedactQueuedTask -> RedactEventTaskInfo( + redactionLocalEcho = task.redactionLocalEchoId, + order = order + ) + else -> null } } @@ -90,7 +90,7 @@ internal class QueueMemento @Inject constructor(context: Context, ?.forEach { info -> try { when (info) { - is SendEventTaskInfo -> { + is SendEventTaskInfo -> { localEchoRepository.getUpToDateEcho(info.localEchoId)?.let { if (it.sendState.isSending() && it.eventId != null && it.roomId != null) { localEchoRepository.updateSendState(it.eventId, it.roomId, SendState.UNSENT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 642f68c15b..63691d9207 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.room.state import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SendStateTask : Task { @@ -34,11 +34,11 @@ internal interface SendStateTask : Task { internal class DefaultSendStateTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SendStateTask { override suspend fun execute(params: SendStateTask.Params) { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = if (params.stateKey == null) { roomAPI.sendStateEvent( roomId = params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt index 013fc86d5c..c3b5c3f78f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.room.tags import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface AddTagToRoomTask : Task { @@ -35,11 +35,11 @@ internal interface AddTagToRoomTask : Task { internal class DefaultAddTagToRoomTask @Inject constructor( private val roomAPI: RoomAPI, @UserId private val userId: String, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : AddTagToRoomTask { override suspend fun execute(params: AddTagToRoomTask.Params) { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.putTag( userId = userId, roomId = params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt index b22355d431..d578d21fde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.room.tags import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteTagFromRoomTask : Task { @@ -34,11 +34,11 @@ internal interface DeleteTagFromRoomTask : Task(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.deleteTag( userId = userId, roomId = params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 86b0497bd0..ae90282d52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -23,10 +23,8 @@ import io.realm.RealmConfiguration import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType @@ -53,6 +51,7 @@ import org.matrix.android.sdk.internal.database.query.filterEvents import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.Debouncer @@ -79,14 +78,13 @@ internal class DefaultTimeline( private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, private val hiddenReadReceipts: TimelineHiddenReadReceipts, - private val eventBus: EventBus, + private val timelineInput: TimelineInput, private val eventDecryptor: TimelineEventDecryptor, - private val realmSessionProvider: RealmSessionProvider -) : Timeline, TimelineHiddenReadReceipts.Delegate { - - data class OnNewTimelineEvents(val roomId: String, val eventIds: List) - data class OnLocalEchoCreated(val roomId: String, val timelineEvent: TimelineEvent) - data class OnLocalEchoUpdated(val roomId: String, val eventId: String, val sendState: SendState) + private val realmSessionProvider: RealmSessionProvider, + private val loadRoomMembersTask: LoadRoomMembersTask +) : Timeline, + TimelineHiddenReadReceipts.Delegate, + TimelineInput.Listener { companion object { val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD") @@ -158,7 +156,7 @@ internal class DefaultTimeline( override fun start() { if (isStarted.compareAndSet(false, true)) { Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") - eventBus.register(this) + timelineInput.listeners.add(this) BACKGROUND_HANDLER.post { eventDecryptor.start() val realm = Realm.getInstance(realmConfiguration) @@ -184,6 +182,13 @@ internal class DefaultTimeline( if (settings.shouldHandleHiddenReadReceipts()) { hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) } + + loadRoomMembersTask + .configureWith(LoadRoomMembersTask.Params(roomId)) { + this.callback = NoOpMatrixCallback() + } + .executeBy(taskExecutor) + isReady.set(true) } } @@ -196,7 +201,7 @@ internal class DefaultTimeline( override fun dispose() { if (isStarted.compareAndSet(true, false)) { isReady.set(false) - eventBus.unregister(this) + timelineInput.listeners.remove(this) Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") cancelableBag.cancel() BACKGROUND_HANDLER.removeCallbacksAndMessages(null) @@ -313,25 +318,22 @@ internal class DefaultTimeline( postSnapshot() } - @Subscribe(threadMode = ThreadMode.MAIN) - fun onNewTimelineEvents(onNewTimelineEvents: OnNewTimelineEvents) { - if (isLive && onNewTimelineEvents.roomId == roomId) { + override fun onNewTimelineEvents(roomId: String, eventIds: List) { + if (isLive && this.roomId == roomId) { listeners.forEach { - it.onNewTimelineEvents(onNewTimelineEvents.eventIds) + it.onNewTimelineEvents(eventIds) } } } - @Subscribe(threadMode = ThreadMode.MAIN) - fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated) { - if (uiEchoManager.onLocalEchoCreated(onLocalEchoCreated)) { + override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { + if (uiEchoManager.onLocalEchoCreated(roomId, timelineEvent)) { postSnapshot() } } - @Subscribe(threadMode = ThreadMode.MAIN) - fun onLocalEchoUpdated(onLocalEchoUpdated: OnLocalEchoUpdated) { - if (uiEchoManager.onLocalEchoUpdated(onLocalEchoUpdated)) { + override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) { + if (uiEchoManager.onLocalEchoUpdated(roomId, eventId, sendState)) { postSnapshot() } } @@ -848,11 +850,11 @@ internal class DefaultTimeline( } } - fun onLocalEchoUpdated(onLocalEchoUpdated: OnLocalEchoUpdated): Boolean { - if (isLive && onLocalEchoUpdated.roomId == roomId) { - val existingState = inMemorySendingStates[onLocalEchoUpdated.eventId] - inMemorySendingStates[onLocalEchoUpdated.eventId] = onLocalEchoUpdated.sendState - if (existingState != onLocalEchoUpdated.sendState) { + fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState): Boolean { + if (isLive && roomId == this@DefaultTimeline.roomId) { + val existingState = inMemorySendingStates[eventId] + inMemorySendingStates[eventId] = sendState + if (existingState != sendState) { return true } } @@ -860,22 +862,22 @@ internal class DefaultTimeline( } // return true if should update - fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated): Boolean { + fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent): Boolean { var postSnapshot = false - if (isLive && onLocalEchoCreated.roomId == roomId) { + if (isLive && roomId == this@DefaultTimeline.roomId) { // Manage some ui echos (do it before filter because actual event could be filtered out) - when (onLocalEchoCreated.timelineEvent.root.getClearType()) { + when (timelineEvent.root.getClearType()) { EventType.REDACTION -> { } EventType.REACTION -> { - val content = onLocalEchoCreated.timelineEvent.root.content?.toModel() + val content = timelineEvent.root.content?.toModel() if (RelationType.ANNOTATION == content?.relatesTo?.type) { val reaction = content.relatesTo.key val relatedEventID = content.relatesTo.eventId inMemoryReactions.getOrPut(relatedEventID) { mutableListOf() } .add( ReactionUiEchoData( - localEchoId = onLocalEchoCreated.timelineEvent.eventId, + localEchoId = timelineEvent.eventId, reactedOnEventId = relatedEventID, reaction = reaction ) @@ -888,12 +890,12 @@ internal class DefaultTimeline( } // do not add events that would have been filtered - if (listOf(onLocalEchoCreated.timelineEvent).filterEventsWithSettings().isNotEmpty()) { + if (listOf(timelineEvent).filterEventsWithSettings().isNotEmpty()) { listeners.forEach { - it.onNewTimelineEvents(listOf(onLocalEchoCreated.timelineEvent.eventId)) + it.onNewTimelineEvents(listOf(timelineEvent.eventId)) } - Timber.v("On local echo created: ${onLocalEchoCreated.timelineEvent.eventId}") - inMemorySendingEvents.add(0, onLocalEchoCreated.timelineEvent) + Timber.v("On local echo created: ${timelineEvent.eventId}") + inMemorySendingEvents.add(0, timelineEvent) postSnapshot = true } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 783aa53ddf..fce09cc97c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -23,7 +23,6 @@ import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy import io.realm.Sort import io.realm.kotlin.where -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -39,19 +38,21 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.task.TaskExecutor internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String, @SessionDatabase private val monarchy: Monarchy, private val realmSessionProvider: RealmSessionProvider, - private val eventBus: EventBus, + private val timelineInput: TimelineInput, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, + private val loadRoomMembersTask: LoadRoomMembersTask ) : TimelineService { @AssistedInject.Factory @@ -70,10 +71,11 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv timelineEventMapper = timelineEventMapper, settings = settings, hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), - eventBus = eventBus, + timelineInput = timelineInput, eventDecryptor = eventDecryptor, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, - realmSessionProvider = realmSessionProvider + realmSessionProvider = realmSessionProvider, + loadRoomMembersTask = loadRoomMembersTask ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt index d1bfa1adcb..76c4b3812c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt @@ -20,12 +20,12 @@ import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.query.findIncludingEvent import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.filter.FilterRepository import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface FetchTokenAndPaginateTask : Task { @@ -43,12 +43,12 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val filterRepository: FilterRepository, private val paginationTask: PaginationTask, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : FetchTokenAndPaginateTask { override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val response = executeRequest(eventBus) { + val response = executeRequest(globalErrorReceiver) { apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) } val fromToken = if (params.direction == PaginationDirection.FORWARDS) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt index 7a611dd350..d02a7bafe9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt @@ -16,11 +16,11 @@ package org.matrix.android.sdk.internal.session.room.timeline +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.filter.FilterRepository import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetContextOfEventTask : Task { @@ -35,12 +35,12 @@ internal class DefaultGetContextOfEventTask @Inject constructor( private val roomAPI: RoomAPI, private val filterRepository: FilterRepository, private val tokenChunkEventPersistor: TokenChunkEventPersistor, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetContextOfEventTask { override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val response = executeRequest(eventBus) { + val response = executeRequest(globalErrorReceiver) { // We are limiting the response to the event with eventId to be sure we don't have any issue with potential merging process. apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index acac3929ae..b8585b1e74 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -17,17 +17,17 @@ package org.matrix.android.sdk.internal.session.room.timeline import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject // TODO Add parent task internal class GetEventTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : Task { internal data class Params( @@ -36,7 +36,7 @@ internal class GetEventTask @Inject constructor( ) override suspend fun execute(params: Params): Event { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = roomAPI.getEvent(params.roomId, params.eventId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt index b663d03bd7..1f99893e17 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt @@ -16,11 +16,11 @@ package org.matrix.android.sdk.internal.session.room.timeline +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.filter.FilterRepository import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface PaginationTask : Task { @@ -37,12 +37,12 @@ internal class DefaultPaginationTask @Inject constructor( private val roomAPI: RoomAPI, private val filterRepository: FilterRepository, private val tokenChunkEventPersistor: TokenChunkEventPersistor, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : PaginationTask { override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val chunk = executeRequest(eventBus) { + val chunk = executeRequest(globalErrorReceiver) { isRetryable = true apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt new file mode 100644 index 0000000000..002ab1dd8a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt @@ -0,0 +1,45 @@ +/* + * 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. + * 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.internal.session.room.timeline + +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.session.SessionScope +import javax.inject.Inject + +@SessionScope +internal class TimelineInput @Inject constructor() { + fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { + listeners.toSet().forEach { it.onLocalEchoCreated(roomId, timelineEvent) } + } + + fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) { + listeners.toSet().forEach { it.onLocalEchoUpdated(roomId, eventId, sendState) } + } + + fun onNewTimelineEvents(roomId: String, eventIds: List) { + listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) } + } + + val listeners = mutableSetOf() + + internal interface Listener { + fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) + fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) + fun onNewTimelineEvents(roomId: String, eventIds: List) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt index c8cbb08e2c..3b56d04872 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task import kotlinx.coroutines.delay -import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import javax.inject.Inject internal interface SendTypingTask : Task { @@ -38,13 +38,13 @@ internal interface SendTypingTask : Task { internal class DefaultSendTypingTask @Inject constructor( private val roomAPI: RoomAPI, @UserId private val userId: String, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SendTypingTask { override suspend fun execute(params: SendTypingTask.Params) { delay(params.delay ?: -1) - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.sendTypingState( params.roomId, userId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt index d0439ce7f9..0c0e6a8ed0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt @@ -31,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.query.TimelineEventFilter import org.matrix.android.sdk.internal.database.query.whereType import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.filter.FilterFactory import org.matrix.android.sdk.internal.session.room.RoomAPI @@ -39,7 +40,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetUploadsTask : Task { @@ -56,7 +56,7 @@ internal class DefaultGetUploadsTask @Inject constructor( private val roomAPI: RoomAPI, private val tokenStore: SyncTokenStore, @SessionDatabase private val monarchy: Monarchy, - private val eventBus: EventBus) + private val globalErrorReceiver: GlobalErrorReceiver) : GetUploadsTask { override suspend fun execute(params: GetUploadsTask.Params): GetUploadsResult { @@ -86,7 +86,7 @@ internal class DefaultGetUploadsTask @Inject constructor( val since = params.since ?: tokenStore.getLastToken() ?: throw IllegalStateException("No token available") val filter = FilterFactory.createUploadsFilter(params.numberOfEvents).toJSONString() - val chunk = executeRequest(eventBus) { + val chunk = executeRequest(globalErrorReceiver) { apiCall = roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt index 4f574e5ead..402602e4d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.session.search -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.search.EventAndSender import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody import org.matrix.android.sdk.internal.session.search.request.SearchRequestCategories @@ -47,11 +47,11 @@ internal interface SearchTask : Task { internal class DefaultSearchTask @Inject constructor( private val searchAPI: SearchAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SearchTask { override suspend fun execute(params: SearchTask.Params): SearchResult { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { val searchRequestBody = SearchRequestBody( searchCategories = SearchRequestCategories( roomEvents = SearchRequestRoomEvents( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt index 3bed0bdbff..2c3cd5d270 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt @@ -20,9 +20,9 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SignInAgainTask : Task { @@ -35,11 +35,11 @@ internal class DefaultSignInAgainTask @Inject constructor( private val signOutAPI: SignOutAPI, private val sessionParams: SessionParams, private val sessionParamsStore: SessionParamsStore, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SignInAgainTask { override suspend fun execute(params: SignInAgainTask.Params) { - val newCredentials = executeRequest(eventBus) { + val newCredentials = executeRequest(globalErrorReceiver) { apiCall = signOutAPI.loginAgain( PasswordLoginParams.userIdentifier( // Reuse the same userId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt index 153ea5a6fd..0cb8704782 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt @@ -18,11 +18,11 @@ package org.matrix.android.sdk.internal.session.signout import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.cleanup.CleanupSession import org.matrix.android.sdk.internal.session.identity.IdentityDisconnectTask import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import timber.log.Timber import java.net.HttpURLConnection import javax.inject.Inject @@ -35,7 +35,7 @@ internal interface SignOutTask : Task { internal class DefaultSignOutTask @Inject constructor( private val signOutAPI: SignOutAPI, - private val eventBus: EventBus, + private val globalErrorReceiver: GlobalErrorReceiver, private val identityDisconnectTask: IdentityDisconnectTask, private val cleanupSession: CleanupSession ) : SignOutTask { @@ -45,7 +45,7 @@ internal class DefaultSignOutTask @Inject constructor( if (params.signOutFromHomeserver) { Timber.d("SignOut: send request...") try { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = signOutAPI.signOut() } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index b1b2f65dc2..456b0f9c26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.sync import io.realm.Realm import io.realm.kotlin.createObject -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.R import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event @@ -55,8 +54,8 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembers import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler import org.matrix.android.sdk.internal.session.room.read.FullyReadContent import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater -import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.internal.session.sync.model.RoomSync @@ -75,7 +74,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private val roomTypingUsersHandler: RoomTypingUsersHandler, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, @UserId private val userId: String, - private val eventBus: EventBus) { + private val timelineInput: TimelineInput) { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() @@ -348,7 +347,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } // posting new events to timeline if any is registered - eventBus.post(DefaultTimeline.OnNewTimelineEvents(roomId = roomId, eventIds = eventIds)) + timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds) return chunkEntity } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index b4fd6e7386..7c38230065 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.session.sync -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.R import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.TimeOutInterceptor import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService @@ -48,7 +48,7 @@ internal class DefaultSyncTask @Inject constructor( private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, private val userStore: UserStore, private val syncTaskSequencer: SyncTaskSequencer, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SyncTask { override suspend fun execute(params: SyncTask.Params) = syncTaskSequencer.post { @@ -81,7 +81,7 @@ internal class DefaultSyncTask @Inject constructor( val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) - val syncResponse = executeRequest(eventBus) { + val syncResponse = executeRequest(globalErrorReceiver) { apiCall = syncAPI.sync( params = requestParams, readTimeOut = readTimeOut diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt index 6d100a71f9..9d854229df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber +import java.net.SocketTimeoutException import java.util.concurrent.atomic.AtomicBoolean /** @@ -68,14 +69,12 @@ abstract class SyncService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Timber.i("## Sync: onStartCommand [$this] $intent with action: ${intent?.action}") - - // We should start we have to ensure we fulfill contract to show notification - // for foreground service (as per design for this service) - // TODO can we check if it's really in foreground - onStart(isInitialSync) when (intent?.action) { ACTION_STOP -> { Timber.i("## Sync: stop command received") + // We should start we have to ensure we fulfill contract to show notification + // for foreground service (as per design for this service) + onStart(isInitialSync) // If it was periodic we ensure that it will not reschedule itself preventReschedule = true // we don't want to cancel initial syncs, let it finish @@ -85,11 +84,12 @@ abstract class SyncService : Service() { } else -> { val isInit = initialize(intent) + onStart(isInitialSync) if (isInit) { periodic = intent?.getBooleanExtra(EXTRA_PERIODIC, false) ?: false val onNetworkBack = intent?.getBooleanExtra(EXTRA_NETWORK_BACK_RESTART, false) ?: false Timber.d("## Sync: command received, periodic: $periodic networkBack: $onNetworkBack") - if (onNetworkBack && !backgroundDetectionObserver.isInBackground) { + if (!isInitialSync && onNetworkBack && !backgroundDetectionObserver.isInBackground) { // the restart after network occurs while the app is in foreground // so just stop. It will be restarted when entering background preventReschedule = true @@ -165,10 +165,16 @@ abstract class SyncService : Service() { preventReschedule = true } if (throwable is Failure.NetworkConnection) { - // Network is off, no need to reschedule endless alarms :/ + // Timeout is not critical, so retry as soon as possible. + val retryDelay = if (isInitialSync || throwable.cause is SocketTimeoutException) { + 0 + } else { + syncDelaySeconds + } + // Network might be off, no need to reschedule endless alarms :/ preventReschedule = true - // Instead start a work to restart background sync when network is back - onNetworkError(sessionId ?: "", isInitialSync, syncTimeoutSeconds, syncDelaySeconds) + // Instead start a work to restart background sync when network is on + onNetworkError(sessionId ?: "", isInitialSync, syncTimeoutSeconds, retryDelay) } // JobCancellation could be caught here when onDestroy cancels the coroutine context if (isRunning.get()) stopMe() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt index 69b3698b4f..fd1ed741e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt @@ -17,20 +17,20 @@ package org.matrix.android.sdk.internal.session.thirdparty import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetThirdPartyProtocolsTask : Task> internal class DefaultGetThirdPartyProtocolsTask @Inject constructor( private val thirdPartyAPI: ThirdPartyAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : GetThirdPartyProtocolsTask { override suspend fun execute(params: Unit): Map { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = thirdPartyAPI.thirdPartyProtocols() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt index aa6a46938f..01a8b57678 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.session.thirdparty -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -32,11 +32,11 @@ internal interface GetThirdPartyUserTask : Task { - return executeRequest(eventBus) { + return executeRequest(globalErrorReceiver) { apiCall = thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt index d1393c8b37..26e8d3380a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.internal.session.user.accountdata import com.zhuinden.monarchy.Monarchy -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.sync.model.accountdata.IgnoredUsersContent import org.matrix.android.sdk.internal.task.Task @@ -40,7 +40,7 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val saveIgnoredUsersTask: SaveIgnoredUsersTask, @UserId private val userId: String, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : UpdateIgnoredUserIdsTask { override suspend fun execute(params: UpdateIgnoredUserIdsTask.Params) { @@ -63,7 +63,7 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor( val list = ignoredUserIds.toList() val body = IgnoredUsersContent.createWithUserIds(list) - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 80ab79b228..dba28253a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -24,8 +24,8 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTe import org.matrix.android.sdk.internal.session.sync.model.accountdata.BreadcrumbsContent import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdateUserAccountDataTask : Task { @@ -100,11 +100,11 @@ internal interface UpdateUserAccountDataTask : Task> { @@ -34,11 +34,11 @@ internal interface SearchUserTask : Task> { internal class DefaultSearchUserTask @Inject constructor( private val searchUserAPI: SearchUserAPI, - private val eventBus: EventBus + private val globalErrorReceiver: GlobalErrorReceiver ) : SearchUserTask { override suspend fun execute(params: SearchUserTask.Params): List { - val response = executeRequest(eventBus) { + val response = executeRequest(globalErrorReceiver) { apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) } return response.users.map { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt index 422615af2d..ae807ce30f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt @@ -25,10 +25,10 @@ import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFie import org.matrix.android.sdk.internal.database.query.whereStateKey import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface CreateWidgetTask : Task { @@ -43,10 +43,10 @@ internal interface CreateWidgetTask : Task { internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy, private val roomAPI: RoomAPI, @UserId private val userId: String, - private val eventBus: EventBus) : CreateWidgetTask { + private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask { override suspend fun execute(params: CreateWidgetTask.Params) { - executeRequest(eventBus) { + executeRequest(globalErrorReceiver) { apiCall = roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY, diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index ff5f69db5c..420f57c57a 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===86 +enum class===87 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 5035811b24..d1289563e9 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -243,6 +243,7 @@ + diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 17d79e3655..4ce4e9a210 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -332,11 +332,6 @@ SOFTWARE.
Copyright 2018 The diff-match-patch Authors. https://github.com/google/diff-match-patch -
  • - EventBus -
    - Copyright (C) 2012-2017 Markus Junginger, greenrobot (http://greenrobot.org) -
  • LazyThreeTenBp
    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 f56a6a3d70..dc4a236f08 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 @@ -28,6 +28,7 @@ import im.vector.app.features.MainActivity import im.vector.app.features.call.CallControlsBottomSheet import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.conference.VectorJitsiActivity +import im.vector.app.features.call.transfer.CallTransferActivity import im.vector.app.features.createdirect.CreateDirectRoomActivity import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.quads.SharedSecureStorageActivity @@ -146,6 +147,7 @@ interface ScreenComponent { fun inject(activity: VectorJitsiActivity) fun inject(activity: SearchActivity) fun inject(activity: UserCodeActivity) + fun inject(activity: CallTransferActivity) /* ========================================================================================== * BottomSheets 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 fb8b1eac07..23d6b618fe 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 @@ -38,6 +38,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.HomeRoomListDataSource import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +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.login.ReAuthHelper @@ -158,6 +159,8 @@ interface VectorComponent { fun webRtcCallManager(): WebRtcCallManager + fun roomSummaryHolder(): RoomSummariesHolder + @Component.Factory interface Factory { fun create(@BindsInstance context: Context): VectorComponent diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index bed2e0b850..8409021845 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -22,7 +22,7 @@ import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap import im.vector.app.core.platform.ConfigurationViewModel -import im.vector.app.features.call.SharedActiveCallViewModel +import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel @@ -85,8 +85,8 @@ interface ViewModelModule { @Binds @IntoMap - @ViewModelKey(SharedActiveCallViewModel::class) - fun bindSharedActiveCallViewModel(viewModel: SharedActiveCallViewModel): ViewModel + @ViewModelKey(SharedKnownCallsViewModel::class) + fun bindSharedActiveCallViewModel(viewModel: SharedKnownCallsViewModel): ViewModel @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt index 3dceec48ef..218a22533a 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt @@ -18,6 +18,7 @@ package im.vector.app.core.epoxy import android.animation.ObjectAnimator import android.text.TextUtils +import android.text.method.MovementMethod import android.widget.ImageView import android.widget.TextView import androidx.core.view.doOnPreDraw @@ -36,6 +37,9 @@ abstract class ExpandableTextItem : VectorEpoxyModel( @EpoxyAttribute var maxLines: Int = 3 + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var movementMethod: MovementMethod? = null + private var isExpanded = false private var expandedLines = 0 @@ -43,6 +47,7 @@ abstract class ExpandableTextItem : VectorEpoxyModel( super.bind(holder) holder.content.text = content holder.content.copyOnLongClick() + holder.content.movementMethod = movementMethod holder.content.doOnPreDraw { if (holder.content.lineCount > maxLines) { diff --git a/vector/src/main/java/im/vector/app/core/extensions/Set.kt b/vector/src/main/java/im/vector/app/core/extensions/Set.kt index a78fb85a1d..e1787076b9 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Set.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Set.kt @@ -17,10 +17,18 @@ package im.vector.app.core.extensions // Create a new Set including the provided element if not already present, or removing the element if already present -fun Set.toggle(element: T): Set { +fun Set.toggle(element: T, singleElement: Boolean = false): Set { return if (contains(element)) { - minus(element) + if (singleElement) { + emptySet() + } else { + minus(element) + } } else { - plus(element) + if (singleElement) { + setOf(element) + } else { + plus(element) + } } } diff --git a/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt index 38977d33ba..5037f78445 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt @@ -16,26 +16,6 @@ package im.vector.app.core.extensions -import java.net.URLEncoder - -/** - * Append param and value to a Url, using "?" or "&". Value parameter will be encoded - * Return this for chaining purpose - */ -fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder { - if (contains("?")) { - append("&") - } else { - append("?") - } - - append(param) - append("=") - append(URLEncoder.encode(value, "utf-8")) - - return this -} - /** * Ex: "https://matrix.org/" -> "matrix.org" */ diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index a585e8ea77..7ec04e32e8 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -42,6 +42,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding import com.bumptech.glide.util.Util import com.google.android.material.snackbar.Snackbar +import com.jakewharton.rxbinding3.view.clicks import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder @@ -84,6 +85,7 @@ import io.reactivex.disposables.Disposable import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError import timber.log.Timber +import java.util.concurrent.TimeUnit import kotlin.system.measureTimeMillis abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { @@ -113,6 +115,18 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScr .disposeOnDestroy() } + /* ========================================================================================== + * Views + * ========================================================================================== */ + + protected fun View.debouncedClicks(onClicked: () -> Unit) { + clicks() + .throttleFirst(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { onClicked() } + .disposeOnDestroy() + } + /* ========================================================================================== * DATA * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt index a5a59dc0ba..7571b6a205 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt @@ -22,6 +22,7 @@ import android.content.Intent import android.os.Binder import android.support.v4.media.session.MediaSessionCompat import android.view.KeyEvent +import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.media.session.MediaButtonReceiver import com.airbnb.mvrx.MvRx @@ -46,7 +47,9 @@ import timber.log.Timber class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener, BluetoothHeadsetReceiver.EventListener { private val connections = mutableMapOf() + private val knownCalls = mutableSetOf() + private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationUtils: NotificationUtils private lateinit var callManager: WebRtcCallManager private lateinit var avatarRenderer: AvatarRenderer @@ -74,6 +77,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe override fun onCreate() { super.onCreate() + notificationManager = NotificationManagerCompat.from(this) notificationUtils = vectorComponent().notificationUtils() callManager = vectorComponent().webRtcCallManager() avatarRenderer = vectorComponent().avatarRenderer() @@ -130,7 +134,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe callRingPlayerOutgoing?.stop() displayCallInProgressNotification(intent) } - ACTION_NO_ACTIVE_CALL -> hideCallNotifications() ACTION_CALL_CONNECTING -> { // lower notification priority displayCallInProgressNotification(intent) @@ -138,9 +141,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe callRingPlayerIncoming?.stop() callRingPlayerOutgoing?.stop() } - ACTION_ONGOING_CALL_BG -> { - // there is an ongoing call but call activity is in background - displayCallOnGoingInBackground(intent) + ACTION_CALL_TERMINATED -> { + handleCallTerminated(intent) } else -> { // Should not happen @@ -166,11 +168,15 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe Timber.v("## VOIP displayIncomingCallNotification $intent") val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" val call = callManager.getCallById(callId) ?: return + if (knownCalls.contains(callId)) { + Timber.v("Call already notified $callId$") + return + } val isVideoCall = call.mxCall.isVideoCall val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false) val opponentMatrixItem = getOpponentMatrixItem(call) Timber.v("displayIncomingCallNotification : display the dedicated notification") - val incomingCallAlert = IncomingCallAlert(INCOMING_CALL_ALERT_UID, + val incomingCallAlert = IncomingCallAlert(callId, shouldBeDisplayedIn = { activity -> if (activity is RoomDetailActivity) { call.roomId != activity.currentRoomId @@ -195,7 +201,27 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId, fromBg = fromBg ) - startForeground(NOTIFICATION_ID, notification) + if (knownCalls.isEmpty()) { + startForeground(callId.hashCode(), notification) + } else { + notificationManager.notify(callId.hashCode(), notification) + } + knownCalls.add(callId) + } + + private fun handleCallTerminated(intent: Intent) { + val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" + if (!knownCalls.remove(callId)) { + Timber.v("Call terminated for unknown call $callId$") + return + } + val notification = notificationUtils.buildCallEndedNotification() + notificationManager.notify(callId.hashCode(), notification) + alertManager.cancelAlert(callId) + if (knownCalls.isEmpty()) { + mediaSession?.isActive = false + myStopSelf() + } } private fun showCallScreen(call: WebRtcCall, mode: String) { @@ -210,13 +236,22 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe private fun displayOutgoingRingingCallNotification(intent: Intent) { val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return val call = callManager.getCallById(callId) ?: return + if (knownCalls.contains(callId)) { + Timber.v("Call already notified $callId$") + return + } val opponentMatrixItem = getOpponentMatrixItem(call) Timber.v("displayOutgoingCallNotification : display the dedicated notification") val notification = notificationUtils.buildOutgoingRingingCallNotification( mxCall = call.mxCall, title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId ) - startForeground(NOTIFICATION_ID, notification) + if (knownCalls.isEmpty()) { + startForeground(callId.hashCode(), notification) + } else { + notificationManager.notify(callId.hashCode(), notification) + } + knownCalls.add(callId) } /** @@ -226,44 +261,17 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe Timber.v("## VOIP displayCallInProgressNotification") val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" val call = callManager.getCallById(callId) ?: return + if (!knownCalls.contains(callId)) { + Timber.v("Call in progress for unknown call $callId$") + return + } val opponentMatrixItem = getOpponentMatrixItem(call) - alertManager.cancelAlert(INCOMING_CALL_ALERT_UID) + alertManager.cancelAlert(callId) val notification = notificationUtils.buildPendingCallNotification( mxCall = call.mxCall, title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId ) - startForeground(NOTIFICATION_ID, notification) - // mCallIdInProgress = callId - } - - /** - * Display a call in progress notification. - */ - private fun displayCallOnGoingInBackground(intent: Intent) { - Timber.v("## VOIP displayCallInProgressNotification") - val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return - val call = callManager.getCallById(callId) ?: return - val opponentMatrixItem = getOpponentMatrixItem(call) - - val notification = notificationUtils.buildPendingCallNotification( - mxCall = call.mxCall, - title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId, - fromBg = true) - startForeground(NOTIFICATION_ID, notification) - // mCallIdInProgress = callId - } - - /** - * Hide the permanent call notifications - */ - private fun hideCallNotifications() { - val notification = notificationUtils.buildCallEndedNotification() - alertManager.cancelAlert(INCOMING_CALL_ALERT_UID) - mediaSession?.isActive = false - // It's mandatory to startForeground to avoid crash - startForeground(NOTIFICATION_ID, notification) - - myStopSelf() + notificationManager.notify(callId.hashCode(), notification) } fun addConnection(callConnection: CallConnection) { @@ -277,12 +285,11 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe companion object { private const val NOTIFICATION_ID = 6480 - private const val INCOMING_CALL_ALERT_UID = "INCOMING_CALL_ALERT_UID" private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL" private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL" private const val ACTION_CALL_CONNECTING = "im.vector.app.core.services.CallService.ACTION_CALL_CONNECTING" private const val ACTION_ONGOING_CALL = "im.vector.app.core.services.CallService.ACTION_ONGOING_CALL" - private const val ACTION_ONGOING_CALL_BG = "im.vector.app.core.services.CallService.ACTION_ONGOING_CALL_BG" + private const val ACTION_CALL_TERMINATED = "im.vector.app.core.services.CallService.ACTION_CALL_TERMINATED" private const val ACTION_NO_ACTIVE_CALL = "im.vector.app.core.services.CallService.NO_ACTIVE_CALL" // private const val ACTION_ACTIVITY_VISIBLE = "im.vector.app.core.services.CallService.ACTION_ACTIVITY_VISIBLE" // private const val ACTION_STOP_RINGING = "im.vector.app.core.services.CallService.ACTION_STOP_RINGING" @@ -302,17 +309,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe ContextCompat.startForegroundService(context, intent) } - fun onOnGoingCallBackground(context: Context, - callId: String) { - val intent = Intent(context, CallService::class.java) - .apply { - action = ACTION_ONGOING_CALL_BG - putExtra(EXTRA_CALL_ID, callId) - } - - ContextCompat.startForegroundService(context, intent) - } - fun onOutgoingCallRinging(context: Context, callId: String) { val intent = Intent(context, CallService::class.java) @@ -335,10 +331,11 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe ContextCompat.startForegroundService(context, intent) } - fun onNoActiveCall(context: Context) { + fun onCallTerminated(context: Context, callId: String) { val intent = Intent(context, CallService::class.java) .apply { - action = ACTION_NO_ACTIVE_CALL + action = ACTION_CALL_TERMINATED + putExtra(EXTRA_CALL_ID, callId) } ContextCompat.startForegroundService(context, intent) } diff --git a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt index bf78d5b7fb..0950bdf121 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt @@ -21,7 +21,6 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build -import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.getSystemService import androidx.work.Constraints import androidx.work.Data @@ -49,22 +48,19 @@ class VectorSyncService : SyncService() { } } - fun newPeriodicIntent(context: Context, sessionId: String, timeoutSeconds: Int, delayInSeconds: Int): Intent { + fun newPeriodicIntent( + context: Context, + sessionId: String, + timeoutSeconds: Int, + delayInSeconds: Int, + networkBack: Boolean = false + ): Intent { return Intent(context, VectorSyncService::class.java).also { it.putExtra(EXTRA_SESSION_ID, sessionId) it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds) it.putExtra(EXTRA_PERIODIC, true) it.putExtra(EXTRA_DELAY_SECONDS, delayInSeconds) - } - } - - fun newPeriodicNetworkBackIntent(context: Context, sessionId: String, timeoutSeconds: Int, delayInSeconds: Int): Intent { - return Intent(context, VectorSyncService::class.java).also { - it.putExtra(EXTRA_SESSION_ID, sessionId) - it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds) - it.putExtra(EXTRA_PERIODIC, true) - it.putExtra(EXTRA_DELAY_SECONDS, delayInSeconds) - it.putExtra(EXTRA_NETWORK_BACK_RESTART, true) + it.putExtra(EXTRA_NETWORK_BACK_RESTART, networkBack) } } @@ -93,12 +89,12 @@ class VectorSyncService : SyncService() { } override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) { - reschedule(sessionId, timeout, delay) + rescheduleSyncService(sessionId, timeout, delay) } override fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) { Timber.d("## Sync: A network error occured during sync") - val uploadWorkRequest: WorkRequest = + val rescheduleSyncWorkRequest: WorkRequest = OneTimeWorkRequestBuilder() .setInputData(Data.Builder() .putString("sessionId", sessionId) @@ -115,7 +111,7 @@ class VectorSyncService : SyncService() { Timber.d("## Sync: Schedule a work to restart service when network will be on") WorkManager .getInstance(applicationContext) - .enqueue(uploadWorkRequest) + .enqueue(rescheduleSyncWorkRequest) } override fun onDestroy() { @@ -128,42 +124,31 @@ class VectorSyncService : SyncService() { notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE) } - private fun reschedule(sessionId: String, timeout: Int, delay: Int) { - val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(this, 0, newPeriodicIntent(this, sessionId, timeout, delay), 0) - } else { - PendingIntent.getService(this, 0, newPeriodicIntent(this, sessionId, timeout, delay), 0) - } - val firstMillis = System.currentTimeMillis() + delay * 1000L - val alarmMgr = getSystemService()!! - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) - } else { - alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) - } - } - class RestartWhenNetworkOn(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { val sessionId = inputData.getString("sessionId") ?: return Result.failure() val timeout = inputData.getInt("timeout", 6) val delay = inputData.getInt("delay", 60) - - val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(applicationContext, 0, newPeriodicNetworkBackIntent(applicationContext, sessionId, timeout, delay), 0) - } else { - PendingIntent.getService(applicationContext, 0, newPeriodicNetworkBackIntent(applicationContext, sessionId, timeout, delay), 0) - } - val firstMillis = System.currentTimeMillis() + delay * 1000L - val alarmMgr = getSystemService(applicationContext, AlarmManager::class.java)!! - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) - } else { - alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) - } + applicationContext.rescheduleSyncService(sessionId, timeout, delay, true) // Indicate whether the work finished successfully with the Result return Result.success() } } } + +private fun Context.rescheduleSyncService(sessionId: String, timeout: Int, delay: Int, networkBack: Boolean = false) { + val periodicIntent = VectorSyncService.newPeriodicIntent(this, sessionId, timeout, delay, networkBack) + val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PendingIntent.getForegroundService(this, 0, periodicIntent, 0) + } else { + PendingIntent.getService(this, 0, periodicIntent, 0) + } + val firstMillis = System.currentTimeMillis() + delay * 1000L + val alarmMgr = getSystemService()!! + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) + } else { + alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt index b877aea6ae..59418147d7 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.util.AttributeSet -import android.view.LayoutInflater import android.widget.FrameLayout import androidx.core.content.ContextCompat import androidx.core.content.withStyledAttributes @@ -29,7 +28,7 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.databinding.ItemVerificationActionBinding +import im.vector.app.databinding.ViewBottomSheetActionButtonBinding import im.vector.app.features.themes.ThemeUtils class BottomSheetActionButton @JvmOverloads constructor( @@ -37,18 +36,18 @@ class BottomSheetActionButton @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { - val views: ItemVerificationActionBinding + val views: ViewBottomSheetActionButtonBinding var title: String? = null set(value) { field = value - views.itemVerificationActionTitle.setTextOrHide(value) + views.bottomSheetActionTitle.setTextOrHide(value) } var subTitle: String? = null set(value) { field = value - views.itemVerificationActionSubTitle.setTextOrHide(value) + views.bottomSheetActionSubTitle.setTextOrHide(value) } var forceStartPadding: Boolean? = null @@ -56,9 +55,9 @@ class BottomSheetActionButton @JvmOverloads constructor( field = value if (leftIcon == null) { if (forceStartPadding == true) { - views.itemVerificationLeftIcon.isInvisible = true + views.bottomSheetActionLeftIcon.isInvisible = true } else { - views.itemVerificationLeftIcon.isGone = true + views.bottomSheetActionLeftIcon.isGone = true } } } @@ -68,37 +67,38 @@ class BottomSheetActionButton @JvmOverloads constructor( field = value if (value == null) { if (forceStartPadding == true) { - views.itemVerificationLeftIcon.isInvisible = true + views.bottomSheetActionLeftIcon.isInvisible = true } else { - views.itemVerificationLeftIcon.isGone = true + views.bottomSheetActionLeftIcon.isGone = true } - views.itemVerificationLeftIcon.setImageDrawable(null) + views.bottomSheetActionLeftIcon.setImageDrawable(null) } else { - views.itemVerificationLeftIcon.isVisible = true - views.itemVerificationLeftIcon.setImageDrawable(value) + views.bottomSheetActionLeftIcon.isVisible = true + views.bottomSheetActionLeftIcon.setImageDrawable(value) } } var rightIcon: Drawable? = null set(value) { field = value - views.itemVerificationActionIcon.setImageDrawable(value) + views.bottomSheetActionIcon.setImageDrawable(value) } var tint: Int? = null set(value) { field = value - views.itemVerificationLeftIcon.imageTintList = value?.let { ColorStateList.valueOf(value) } + views.bottomSheetActionLeftIcon.imageTintList = value?.let { ColorStateList.valueOf(value) } } var titleTextColor: Int? = null set(value) { field = value - value?.let { views.itemVerificationActionTitle.setTextColor(it) } + value?.let { views.bottomSheetActionTitle.setTextColor(it) } } init { - views = ItemVerificationActionBinding.inflate(LayoutInflater.from(context), this, true) + inflate(context, R.layout.view_bottom_sheet_action_button, this) + views = ViewBottomSheetActionButtonBinding.bind(this) context.withStyledAttributes(attrs, R.styleable.BottomSheetActionButton) { title = getString(R.styleable.BottomSheetActionButton_actionTitle) ?: "" diff --git a/vector/src/main/java/im/vector/app/core/ui/views/CurrentCallsView.kt b/vector/src/main/java/im/vector/app/core/ui/views/CurrentCallsView.kt new file mode 100644 index 0000000000..d1332f18dc --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/views/CurrentCallsView.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020 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.ui.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import im.vector.app.R +import im.vector.app.databinding.ViewCurrentCallsBinding +import im.vector.app.features.call.webrtc.WebRtcCall +import im.vector.app.features.themes.ThemeUtils +import org.matrix.android.sdk.api.session.call.CallState + +class CurrentCallsView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : RelativeLayout(context, attrs, defStyleAttr) { + + interface Callback { + fun onTapToReturnToCall() + } + + val views: ViewCurrentCallsBinding + var callback: Callback? = null + + init { + inflate(context, R.layout.view_current_calls, this) + views = ViewCurrentCallsBinding.bind(this) + setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary)) + setOnClickListener { callback?.onTapToReturnToCall() } + } + + fun render(calls: List, formattedDuration: String) { + val connectedCalls = calls.filter { + it.mxCall.state is CallState.Connected + } + val heldCalls = connectedCalls.filter { + it.isLocalOnHold || it.remoteOnHold + } + if (connectedCalls.isEmpty()) return + views.currentCallsInfo.text = if (connectedCalls.size == heldCalls.size) { + resources.getQuantityString(R.plurals.call_only_paused, heldCalls.size, heldCalls.size) + } else { + if (heldCalls.isEmpty()) { + resources.getString(R.string.call_only_active, formattedDuration) + } else { + resources.getQuantityString(R.plurals.call_one_active_and_other_paused, heldCalls.size, formattedDuration, heldCalls.size) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ActiveCallViewHolder.kt b/vector/src/main/java/im/vector/app/core/ui/views/KnownCallsViewHolder.kt similarity index 61% rename from vector/src/main/java/im/vector/app/core/ui/views/ActiveCallViewHolder.kt rename to vector/src/main/java/im/vector/app/core/ui/views/KnownCallsViewHolder.kt index 193a4b2387..3bd9ce713d 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/ActiveCallViewHolder.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/KnownCallsViewHolder.kt @@ -16,7 +16,6 @@ package im.vector.app.core.ui.views -import android.view.View import androidx.cardview.widget.CardView import androidx.core.view.isVisible import im.vector.app.core.utils.DebouncedClickListener @@ -26,33 +25,47 @@ import im.vector.app.features.call.webrtc.WebRtcCall import org.webrtc.RendererCommon import org.webrtc.SurfaceViewRenderer -class ActiveCallViewHolder { +class KnownCallsViewHolder { private var activeCallPiP: SurfaceViewRenderer? = null - private var activeCallView: ActiveCallView? = null + private var currentCallsView: CurrentCallsView? = null private var pipWrapper: CardView? = null - private var activeCall: WebRtcCall? = null + private var currentCall: WebRtcCall? = null + private var calls: List = emptyList() private var activeCallPipInitialized = false - fun updateCall(activeCall: WebRtcCall?) { - this.activeCall = activeCall - val hasActiveCall = activeCall?.mxCall?.state is CallState.Connected + private val tickListener = object : WebRtcCall.Listener { + override fun onTick(formattedDuration: String) { + currentCallsView?.render(calls, formattedDuration) + } + } + + fun updateCall(currentCall: WebRtcCall?, calls: List) { + activeCallPiP?.let { + this.currentCall?.detachRenderers(listOf(it)) + } + this.currentCall?.removeListener(tickListener) + this.currentCall = currentCall + this.currentCall?.addListener(tickListener) + this.calls = calls + val hasActiveCall = currentCall?.mxCall?.state is CallState.Connected if (hasActiveCall) { - val isVideoCall = activeCall?.mxCall?.isVideoCall == true + val isVideoCall = currentCall?.mxCall?.isVideoCall == true if (isVideoCall) initIfNeeded() - activeCallView?.isVisible = !isVideoCall + currentCallsView?.isVisible = !isVideoCall + currentCallsView?.render(calls, currentCall?.formattedDuration() ?: "") pipWrapper?.isVisible = isVideoCall activeCallPiP?.isVisible = isVideoCall activeCallPiP?.let { - activeCall?.attachViewRenderers(null, it, null) + currentCall?.attachViewRenderers(null, it, null) } } else { - activeCallView?.isVisible = false + currentCallsView?.isVisible = false activeCallPiP?.isVisible = false pipWrapper?.isVisible = false activeCallPiP?.let { - activeCall?.detachRenderers(listOf(it)) + currentCall?.detachRenderers(listOf(it)) } } } @@ -70,30 +83,31 @@ class ActiveCallViewHolder { } } - fun bind(activeCallPiP: SurfaceViewRenderer, activeCallView: ActiveCallView, pipWrapper: CardView, interactionListener: ActiveCallView.Callback) { + fun bind(activeCallPiP: SurfaceViewRenderer, activeCallView: CurrentCallsView, pipWrapper: CardView, interactionListener: CurrentCallsView.Callback) { this.activeCallPiP = activeCallPiP - this.activeCallView = activeCallView + this.currentCallsView = activeCallView this.pipWrapper = pipWrapper - - this.activeCallView?.callback = interactionListener + this.currentCallsView?.callback = interactionListener pipWrapper.setOnClickListener( - DebouncedClickListener(View.OnClickListener { _ -> + DebouncedClickListener({ _ -> interactionListener.onTapToReturnToCall() }) ) + this.currentCall?.addListener(tickListener) } fun unBind() { activeCallPiP?.let { - activeCall?.detachRenderers(listOf(it)) + currentCall?.detachRenderers(listOf(it)) } if (activeCallPipInitialized) { activeCallPiP?.release() } - this.activeCallView?.callback = null + this.currentCallsView?.callback = null + this.currentCall?.removeListener(tickListener) pipWrapper?.setOnClickListener(null) activeCallPiP = null - activeCallView = null + currentCallsView = null pipWrapper = null } } diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ActiveCallView.kt b/vector/src/main/java/im/vector/app/core/ui/views/NonScrollingTextView.kt similarity index 51% rename from vector/src/main/java/im/vector/app/core/ui/views/ActiveCallView.kt rename to vector/src/main/java/im/vector/app/core/ui/views/NonScrollingTextView.kt index 19d1fbb6f6..712432abfd 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/ActiveCallView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/NonScrollingTextView.kt @@ -18,29 +18,14 @@ package im.vector.app.core.ui.views import android.content.Context import android.util.AttributeSet -import android.widget.RelativeLayout -import im.vector.app.R -import im.vector.app.features.themes.ThemeUtils +import androidx.appcompat.widget.AppCompatTextView -class ActiveCallView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RelativeLayout(context, attrs, defStyleAttr) { +class NonScrollingTextView : AppCompatTextView { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - interface Callback { - fun onTapToReturnToCall() - } - - var callback: Callback? = null - - init { - setupView() - } - - private fun setupView() { - inflate(context, R.layout.view_active_call_view, this) - setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary)) - setOnClickListener { callback?.onTapToReturnToCall() } + override fun scrollTo(x: Int, y: Int) { + // NOOP } } diff --git a/vector/src/main/java/im/vector/app/core/utils/CountUpTimer.kt b/vector/src/main/java/im/vector/app/core/utils/CountUpTimer.kt new file mode 100644 index 0000000000..2a9482765c --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/CountUpTimer.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2020 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.utils + +import io.reactivex.Observable +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong + +class CountUpTimer(private val intervalInMs: Long) { + + private val elapsedTime: AtomicLong = AtomicLong() + private val resumed: AtomicBoolean = AtomicBoolean(false) + + private val disposable = Observable.interval(intervalInMs, TimeUnit.MILLISECONDS) + .filter { _ -> resumed.get() } + .doOnNext { _ -> elapsedTime.addAndGet(intervalInMs) } + .subscribe { + tickListener?.onTick(elapsedTime.get()) + } + + var tickListener: TickListener? = null + + fun elapsedTime(): Long { + return elapsedTime.get() + } + + fun pause() { + resumed.set(false) + } + + fun resume() { + resumed.set(true) + } + + fun stop() { + disposable.dispose() + } + + interface TickListener { + fun onTick(milliseconds: Long) + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index 29aa72b11f..b343b5027f 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -44,27 +44,31 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment = MutableLiveData() + val liveKnownCalls: MutableLiveData> = MutableLiveData() - val callStateListener = object : WebRtcCall.Listener { + val callListener = object : WebRtcCall.Listener { override fun onStateUpdate(call: MxCall) { - if (activeCall.value?.callId == call.callId) { - activeCall.postValue(callManager.getCallById(call.callId)) + // post it-self + liveKnownCalls.postValue(liveKnownCalls.value) + } + + override fun onHoldUnhold() { + super.onHoldUnhold() + // post it-self + liveKnownCalls.postValue(liveKnownCalls.value) + } + } + + private val currentCallListener = object : WebRtcCallManager.CurrentCallListener { + override fun onCurrentCallChange(call: WebRtcCall?) { + val knownCalls = callManager.getCalls() + liveKnownCalls.postValue(knownCalls) + knownCalls.forEach { + it.removeListener(callListener) + it.addListener(callListener) } } } - private val listener = object : WebRtcCallManager.CurrentCallListener { - override fun onCurrentCallChange(call: WebRtcCall?) { - activeCall.value?.mxCall?.removeListener(callStateListener) - activeCall.postValue(call) - call?.addListener(callStateListener) + init { + val knownCalls = callManager.getCalls() + liveKnownCalls.postValue(knownCalls) + callManager.addCurrentCallListener(currentCallListener) + knownCalls.forEach { + it.addListener(callListener) } } - init { - activeCall.postValue(callManager.currentCall) - callManager.addCurrentCallListener(listener) - } - override fun onCleared() { - activeCall.value?.removeListener(callStateListener) - callManager.removeCurrentCallListener(listener) + callManager.getCalls().forEach { + it.removeListener(callListener) + } + callManager.removeCurrentCallListener(currentCallListener) super.onCleared() } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 9a217dcd11..77b2120b5f 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -34,10 +34,10 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.Fail import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel +import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.services.CallService import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL import im.vector.app.core.utils.allGranted @@ -52,6 +52,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCallDetail import org.matrix.android.sdk.api.session.call.MxPeerConnectionState @@ -113,7 +114,6 @@ class VectorCallActivity : VectorBaseActivity(), CallContro callArgs = intent.getParcelableExtra(MvRx.KEY_ARG)!! } else { Timber.e("## VOIP missing callArgs for VectorCall Activity") - CallService.onNoActiveCall(this) finish() } @@ -162,8 +162,6 @@ class VectorCallActivity : VectorBaseActivity(), CallContro private fun renderState(state: VectorCallViewState) { Timber.v("## VOIP renderState call $state") if (state.callState is Fail) { - // be sure to clear notification - CallService.onNoActiveCall(this) finish() return } @@ -176,7 +174,8 @@ class VectorCallActivity : VectorBaseActivity(), CallContro views.smallIsHeldIcon.isVisible = false when (callState) { is CallState.Idle, - is CallState.Dialing -> { + is CallState.CreateOffer, + is CallState.Dialing -> { views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true views.callStatusText.setText(R.string.call_ring) @@ -199,7 +198,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } is CallState.Connected -> { if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { - if (state.isLocalOnHold) { + if (state.isLocalOnHold || state.isRemoteOnHold) { views.smallIsHeldIcon.isVisible = true views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true @@ -211,20 +210,20 @@ class VectorCallActivity : VectorBaseActivity(), CallContro views.callStatusText.setText(R.string.call_held_by_you) } else { views.callActionText.isInvisible = true - state.otherUserMatrixItem.invoke()?.let { + state.callInfo.otherUserItem?.let { views.callStatusText.text = getString(R.string.call_held_by_user, it.getBestName()) } } } else { - views.callStatusText.text = null + views.callStatusText.text = state.formattedDuration + configureCallInfo(state) if (callArgs.isVideoCall) { views.callVideoGroup.isVisible = true views.callInfoGroup.isVisible = false - // views.pip_video_view.isVisible = !state.isVideoCaptureInError + views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null } else { views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true - configureCallInfo(state) } } } else { @@ -235,8 +234,6 @@ class VectorCallActivity : VectorBaseActivity(), CallContro views.callStatusText.setText(R.string.call_connecting) views.callConnectingProgress.isVisible = true } - // ensure all attached? - callManager.getCallById(callArgs.callId)?.attachViewRenderers(views.pipRenderer, views.fullscreenRenderer, null) } is CallState.Terminated -> { finish() @@ -247,7 +244,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } private fun configureCallInfo(state: VectorCallViewState, blurAvatar: Boolean = false) { - state.otherUserMatrixItem.invoke()?.let { + state.callInfo.otherUserItem?.let { val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen) avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter) views.participantNameText.text = it.getBestName() @@ -257,10 +254,32 @@ class VectorCallActivity : VectorBaseActivity(), CallContro avatarRenderer.render(it, views.otherMemberAvatar) } } + if (state.otherKnownCallInfo?.otherUserItem == null) { + views.otherKnownCallLayout.isVisible = false + } else { + val otherCall = callManager.getCallById(state.otherKnownCallInfo.callId) + val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen) + avatarRenderer.renderBlur( + matrixItem = state.otherKnownCallInfo.otherUserItem, + imageView = views.otherKnownCallAvatarView, + sampling = 20, + rounded = false, + colorFilter = colorFilter + ) + views.otherKnownCallLayout.isVisible = true + views.otherSmallIsHeldIcon.isVisible = otherCall?.let { it.isLocalOnHold || it.remoteOnHold }.orFalse() + } } private fun configureCallViews() { views.callControlsView.interactionListener = this + views.otherKnownCallAvatarView.setOnClickListener { + withState(callViewModel) { + val otherCall = callManager.getCallById(it.otherKnownCallInfo?.callId ?: "") ?: return@withState + startActivity(newIntent(this, otherCall.mxCall, null)) + finish() + } + } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { @@ -304,7 +323,6 @@ class VectorCallActivity : VectorBaseActivity(), CallContro Timber.v("## VOIP handleViewEvents $event") when (event) { VectorCallViewEvents.DismissNoCall -> { - CallService.onNoActiveCall(this) finish() } is VectorCallViewEvents.ConnectionTimeout -> { @@ -315,6 +333,9 @@ class VectorCallActivity : VectorBaseActivity(), CallContro callback = dialPadCallback }.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG) } + is VectorCallViewEvents.ShowCallTransferScreen -> { + navigator.openCallTransfer(this, callArgs.callId) + } null -> { } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index 05e4ed4d0a..a7ee49e6b8 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt @@ -33,4 +33,5 @@ sealed class VectorCallViewActions : VectorViewModelAction { object HeadSetButtonPressed : VectorCallViewActions() object ToggleCamera : VectorCallViewActions() object ToggleHDSD : VectorCallViewActions() + object InitiateCallTransfer : VectorCallViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt index a376e08983..76c603420f 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt @@ -28,6 +28,7 @@ sealed class VectorCallViewEvents : VectorViewEvents { val current: CallAudioManager.SoundDevice ) : VectorCallViewEvents() object ShowDialPad: VectorCallViewEvents() + object ShowCallTransferScreen: VectorCallViewEvents() // data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() // data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() // object CallAccepted : VectorCallViewEvents() 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 424f54b1bf..56ca8c58c0 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 @@ -20,7 +20,6 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -34,6 +33,7 @@ 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 @@ -56,7 +56,7 @@ class VectorCallViewModel @AssistedInject constructor( override fun onHoldUnhold() { setState { copy( - isLocalOnHold = call?.isLocalOnHold() ?: false, + isLocalOnHold = call?.isLocalOnHold ?: false, isRemoteOnHold = call?.remoteOnHold ?: false ) } @@ -80,6 +80,12 @@ class VectorCallViewModel @AssistedInject constructor( } } + override fun onTick(formattedDuration: String) { + setState { + copy(formattedDuration = formattedDuration) + } + } + override fun onStateUpdate(call: MxCall) { val callState = call.state if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { @@ -109,7 +115,8 @@ class VectorCallViewModel @AssistedInject constructor( } setState { copy( - callState = Success(callState) + callState = Success(callState), + canOpponentBeTransferred = call.capabilities.supportCallTransfer() ) } } @@ -118,10 +125,10 @@ class VectorCallViewModel @AssistedInject constructor( private val currentCallListener = object : WebRtcCallManager.CurrentCallListener { override fun onCurrentCallChange(call: WebRtcCall?) { - // we need to check the state if (call == null) { - // we should dismiss, e.g handled by other session? _viewEvents.post(VectorCallViewEvents.DismissNoCall) + } else { + updateOtherKnownCall(call) } } @@ -142,6 +149,20 @@ class VectorCallViewModel @AssistedInject constructor( } } + private fun updateOtherKnownCall(currentCall: WebRtcCall) { + val otherCall = callManager.getCalls().firstOrNull { + it.callId != currentCall.callId && it.mxCall.state is CallState.Connected + } + setState { + if (otherCall == null) { + copy(otherKnownCallInfo = null) + } else { + val otherUserItem: MatrixItem? = session.getUser(otherCall.mxCall.opponentUserId)?.toMatrixItem() + copy(otherKnownCallInfo = VectorCallViewState.CallInfo(otherCall.callId, otherUserItem)) + } + } + } + init { val webRtcCall = callManager.getCallById(initialState.callId) if (webRtcCall == null) { @@ -161,14 +182,19 @@ class VectorCallViewModel @AssistedInject constructor( copy( isVideoCall = webRtcCall.mxCall.isVideoCall, callState = Success(webRtcCall.mxCall.state), - otherUserMatrixItem = item?.let { Success(it) } ?: Uninitialized, + callInfo = VectorCallViewState.CallInfo(callId, item), soundDevice = currentSoundDevice, + isLocalOnHold = webRtcCall.isLocalOnHold, + isRemoteOnHold = webRtcCall.remoteOnHold, availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(), - isFrontCamera = call?.currentCameraType() == CameraType.FRONT, - canSwitchCamera = call?.canSwitchCamera() ?: false, - isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD + isFrontCamera = webRtcCall.currentCameraType() == CameraType.FRONT, + canSwitchCamera = webRtcCall.canSwitchCamera(), + formattedDuration = webRtcCall.formattedDuration(), + isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD, + canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer() ) } + updateOtherKnownCall(webRtcCall) } } @@ -252,6 +278,11 @@ class VectorCallViewModel @AssistedInject constructor( is VectorCallViewActions.SendDtmfDigit -> { call?.sendDtmfDigit(action.digit) } + VectorCallViewActions.InitiateCallTransfer -> { + _viewEvents.post( + VectorCallViewEvents.ShowCallTransferScreen + ) + } }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index 8742e2cc7c..1d12b780b1 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -36,10 +36,18 @@ data class VectorCallViewState( val canSwitchCamera: Boolean = true, val soundDevice: CallAudioManager.SoundDevice = CallAudioManager.SoundDevice.PHONE, val availableSoundDevices: List = emptyList(), - val otherUserMatrixItem: Async = Uninitialized, - val callState: Async = Uninitialized + val callState: Async = Uninitialized, + val otherKnownCallInfo: CallInfo? = null, + val callInfo: CallInfo = CallInfo(callId), + val formattedDuration: String = "", + val canOpponentBeTransferred: Boolean = false ) : MvRxState { + data class CallInfo( + val callId: String, + val otherUserItem: MatrixItem? = null + ) + constructor(callArgs: CallArgs): this( callId = callArgs.callId, roomId = callArgs.roomId, diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt new file mode 100644 index 0000000000..cbdd9c252b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 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.transfer + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class CallTransferAction : VectorViewModelAction { + data class Connect(val consultFirst: Boolean, val selectedUserId: String) : CallTransferAction() +} diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt new file mode 100644 index 0000000000..89a7caa764 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020 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.transfer + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import android.widget.Toast +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.viewModel +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.extensions.addFragmentToBackstack +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH +import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS +import im.vector.app.core.utils.allGranted +import im.vector.app.core.utils.checkPermissions +import im.vector.app.databinding.ActivityCallTransferBinding +import im.vector.app.features.contactsbook.ContactsBookFragment +import im.vector.app.features.contactsbook.ContactsBookViewModel +import im.vector.app.features.contactsbook.ContactsBookViewState +import im.vector.app.features.userdirectory.UserListFragment +import im.vector.app.features.userdirectory.UserListFragmentArgs +import im.vector.app.features.userdirectory.UserListSharedAction +import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import im.vector.app.features.userdirectory.UserListViewModel +import im.vector.app.features.userdirectory.UserListViewState +import kotlinx.parcelize.Parcelize +import javax.inject.Inject + +@Parcelize +data class CallTransferArgs(val callId: String) : Parcelable + +private const val USER_LIST_FRAGMENT_TAG = "USER_LIST_FRAGMENT_TAG" + +class CallTransferActivity : VectorBaseActivity(), + CallTransferViewModel.Factory, + UserListViewModel.Factory, + ContactsBookViewModel.Factory { + + private lateinit var sharedActionViewModel: UserListSharedActionViewModel + @Inject lateinit var userListViewModelFactory: UserListViewModel.Factory + @Inject lateinit var callTransferViewModelFactory: CallTransferViewModel.Factory + @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory + @Inject lateinit var errorFormatter: ErrorFormatter + + private val callTransferViewModel: CallTransferViewModel by viewModel() + + override fun getBinding() = ActivityCallTransferBinding.inflate(layoutInflater) + + override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) + injector.inject(this) + } + + override fun create(initialState: UserListViewState): UserListViewModel { + return userListViewModelFactory.create(initialState) + } + + override fun create(initialState: CallTransferViewState): CallTransferViewModel { + return callTransferViewModelFactory.create(initialState) + } + + override fun create(initialState: ContactsBookViewState): ContactsBookViewModel { + return contactsBookViewModelFactory.create(initialState) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + waitingView = views.waitingView.waitingView + sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) + sharedActionViewModel + .observe() + .subscribe { sharedAction -> + when (sharedAction) { + UserListSharedAction.OpenPhoneBook -> openPhoneBook() + // not exhaustive because it's a sharedAction + else -> { + } + } + } + .disposeOnDestroy() + if (isFirstCreation()) { + addFragment( + R.id.callTransferFragmentContainer, + UserListFragment::class.java, + UserListFragmentArgs( + title = "", + menuResId = -1, + singleSelection = true, + showInviteActions = false, + showToolbar = false + ), + USER_LIST_FRAGMENT_TAG + ) + } + callTransferViewModel.observeViewEvents { + when (it) { + is CallTransferViewEvents.Dismiss -> finish() + CallTransferViewEvents.Loading -> showWaitingView() + is CallTransferViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure)) + } + } + configureToolbar(views.callTransferToolbar) + views.callTransferToolbar.title = getString(R.string.call_transfer_title) + setupConnectAction() + } + + private fun setupConnectAction() { + views.callTransferConnectAction.debouncedClicks { + val userListFragment = supportFragmentManager.findFragmentByTag(USER_LIST_FRAGMENT_TAG) as? UserListFragment + val selectedUser = userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull() + if (selectedUser != null) { + val action = CallTransferAction.Connect(views.callTransferConsultCheckBox.isChecked, selectedUser) + callTransferViewModel.handle(action) + } + } + } + + private fun openPhoneBook() { + // Check permission first + if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, + this, + PERMISSION_REQUEST_CODE_READ_CONTACTS, + 0)) { + addFragmentToBackstack(R.id.callTransferFragmentContainer, ContactsBookFragment::class.java) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (allGranted(grantResults)) { + if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { + doOnPostResume { addFragmentToBackstack(R.id.callTransferFragmentContainer, ContactsBookFragment::class.java) } + } + } else { + Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show() + } + } + + companion object { + + fun newIntent(context: Context, callId: String): Intent { + return Intent(context, CallTransferActivity::class.java).also { + it.putExtra(MvRx.KEY_ARG, CallTransferArgs(callId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewEvents.kt new file mode 100644 index 0000000000..b110164d1e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewEvents.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 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.transfer + +import im.vector.app.core.platform.VectorViewEvents + +sealed class CallTransferViewEvents : VectorViewEvents { + object Dismiss : CallTransferViewEvents() + object Loading: CallTransferViewEvents() + object FailToTransfer : CallTransferViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt new file mode 100644 index 0000000000..0b83a6bcda --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 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.transfer + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.call.webrtc.WebRtcCall +import im.vector.app.features.call.webrtc.WebRtcCallManager +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.call.CallState +import org.matrix.android.sdk.api.session.call.MxCall + +class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState, + callManager: WebRtcCallManager) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: CallTransferViewState): CallTransferViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: CallTransferViewState): CallTransferViewModel? { + val activity: CallTransferActivity = (viewModelContext as ActivityViewModelContext).activity() + return activity.callTransferViewModelFactory.create(state) + } + } + + private val call = callManager.getCallById(initialState.callId) + private val callListener = object : WebRtcCall.Listener { + override fun onStateUpdate(call: MxCall) { + if (call.state == CallState.Terminated) { + _viewEvents.post(CallTransferViewEvents.Dismiss) + } + } + } + + init { + if (call == null) { + _viewEvents.post(CallTransferViewEvents.Dismiss) + } else { + call.addListener(callListener) + } + } + + override fun onCleared() { + super.onCleared() + call?.removeListener(callListener) + } + + override fun handle(action: CallTransferAction) { + when (action) { + is CallTransferAction.Connect -> transferCall(action) + }.exhaustive + } + + private fun transferCall(action: CallTransferAction.Connect) { + viewModelScope.launch { + try { + _viewEvents.post(CallTransferViewEvents.Loading) + call?.mxCall?.transfer(action.selectedUserId, null) + _viewEvents.post(CallTransferViewEvents.Dismiss) + } catch (failure: Throwable) { + _viewEvents.post(CallTransferViewEvents.FailToTransfer) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewState.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewState.kt new file mode 100644 index 0000000000..2b29d9f6f2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 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.transfer + +import com.airbnb.mvrx.MvRxState + +data class CallTransferViewState( + val callId: String +) : MvRxState { + + constructor(args: CallTransferArgs) : this(callId = args.callId) +} 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 745e6a24c3..c69475e128 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 @@ -20,6 +20,7 @@ import android.content.Context import android.hardware.camera2.CameraManager import androidx.core.content.getSystemService import im.vector.app.core.services.CallService +import im.vector.app.core.utils.CountUpTimer import im.vector.app.features.call.CallAudioManager import im.vector.app.features.call.CameraEventsHandlerAdapter import im.vector.app.features.call.CameraProxy @@ -45,6 +46,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull 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.CallAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent @@ -53,6 +55,7 @@ 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 import org.webrtc.Camera1Enumerator @@ -72,6 +75,7 @@ import org.webrtc.VideoSource import org.webrtc.VideoTrack import timber.log.Timber import java.lang.ref.WeakReference +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.TimeUnit import javax.inject.Provider import kotlin.coroutines.CoroutineContext @@ -88,15 +92,18 @@ class WebRtcCall(val mxCall: MxCall, private val dispatcher: CoroutineContext, private val sessionProvider: Provider, private val peerConnectionFactoryProvider: Provider, + private val onCallBecomeActive: (WebRtcCall) -> Unit, private val onCallEnded: (WebRtcCall) -> Unit) : MxCall.StateListener { interface Listener : MxCall.StateListener { fun onCaptureStateChanged() {} fun onCameraChanged() {} fun onHoldUnhold() {} + fun onTick(formattedDuration: String) {} + override fun onStateUpdate(call: MxCall) {} } - private val listeners = ArrayList() + private val listeners = CopyOnWriteArrayList() fun addListener(listener: Listener) { listeners.add(listener) @@ -128,10 +135,29 @@ class WebRtcCall(val mxCall: MxCall, private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null + private val timer = CountUpTimer(Duration.ofSeconds(1).toMillis()).apply { + tickListener = object : CountUpTimer.TickListener { + override fun onTick(milliseconds: Long) { + val formattedDuration = formatDuration(Duration.ofMillis(milliseconds)) + listeners.forEach { + tryOrNull { it.onTick(formattedDuration) } + } + } + } + } + // Mute status var micMuted = false + private set var videoMuted = false + private set var remoteOnHold = false + private set + var isLocalOnHold = false + private set + + // This value is used to track localOnHold when changing remoteOnHold value + private var wasLocalOnHold = false var offerSdp: CallInviteContent.Offer? = null @@ -204,6 +230,12 @@ class WebRtcCall(val mxCall: MxCall, } } + fun formattedDuration(): String { + return formatDuration( + Duration.ofMillis(timer.elapsedTime()) + ) + } + private fun createPeerConnection(turnServerResponse: TurnServerResponse?) { val peerConnectionFactory = peerConnectionFactoryProvider.get() ?: return val iceServers = mutableListOf().apply { @@ -229,21 +261,9 @@ class WebRtcCall(val mxCall: MxCall, fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) { Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer") -// this.localSurfaceRenderer = WeakReference(localViewRenderer) -// this.remoteSurfaceRenderer = WeakReference(remoteViewRenderer) localSurfaceRenderers.addIfNeeded(localViewRenderer) remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer) - // The call is going to resume from background, we can reduce notif - mxCall - .takeIf { it.state is CallState.Connected } - ?.let { mxCall -> - // Start background service with notification - CallService.onPendingCall( - context = context, - callId = mxCall.callId) - } - GlobalScope.launch(dispatcher) { when (mode) { VectorCallActivity.INCOMING_ACCEPT -> { @@ -292,7 +312,6 @@ class WebRtcCall(val mxCall: MxCall, fun detachRenderers(renderers: List?) { Timber.v("## VOIP detachRenderers") - // currentCall?.localMediaStream?.let { currentCall?.peerConnection?.removeStream(it) } if (renderers.isNullOrEmpty()) { // remove all sinks localSurfaceRenderers.forEach { @@ -312,18 +331,6 @@ class WebRtcCall(val mxCall: MxCall, remoteVideoTrack?.removeSink(it) } } - if (remoteSurfaceRenderers.isEmpty()) { - // The call is going to continue in background, so ensure notification is visible - mxCall - .takeIf { it.state is CallState.Connected } - ?.let { mxCall -> - // Start background service with notification - CallService.onOnGoingCallBackground( - context = context, - callId = mxCall.callId - ) - } - } } private suspend fun setupOutgoingCall() = withContext(dispatcher) { @@ -345,6 +352,9 @@ class WebRtcCall(val mxCall: MxCall, } private suspend fun internalAcceptIncomingCall() = withContext(dispatcher) { + tryOrNull { + onCallBecomeActive(this@WebRtcCall) + } val turnServerResponse = getTurnServer() // Update service state withContext(Dispatchers.Main) { @@ -543,7 +553,7 @@ class WebRtcCall(val mxCall: MxCall, * rather than 'sendonly') * @returns true if the other party has put us on hold */ - fun isLocalOnHold(): Boolean { + private fun computeIsLocalOnHold(): Boolean { if (mxCall.state !is CallState.Connected) return false var callOnHold = true // We consider a call to be on hold only if *all* the tracks are on hold @@ -557,17 +567,32 @@ class WebRtcCall(val mxCall: MxCall, } fun updateRemoteOnHold(onHold: Boolean) { - if (remoteOnHold == onHold) return - remoteOnHold = onHold - val direction = if (onHold) { - RtpTransceiver.RtpTransceiverDirection.INACTIVE - } else { - RtpTransceiver.RtpTransceiverDirection.SEND_RECV + GlobalScope.launch(dispatcher) { + if (remoteOnHold == onHold) return@launch + val direction: RtpTransceiver.RtpTransceiverDirection + if (onHold) { + wasLocalOnHold = isLocalOnHold + remoteOnHold = true + isLocalOnHold = true + direction = RtpTransceiver.RtpTransceiverDirection.INACTIVE + timer.pause() + } else { + remoteOnHold = false + isLocalOnHold = wasLocalOnHold + onCallBecomeActive(this@WebRtcCall) + direction = RtpTransceiver.RtpTransceiverDirection.SEND_RECV + if (!isLocalOnHold) { + timer.resume() + } + } + for (transceiver in peerConnection?.transceivers ?: emptyList()) { + transceiver.direction = direction + } + updateMuteStatus() + listeners.forEach { + tryOrNull { it.onHoldUnhold() } + } } - for (transceiver in peerConnection?.transceivers ?: emptyList()) { - transceiver.direction = direction - } - updateMuteStatus() } fun muteCall(muted: Boolean) { @@ -645,7 +670,10 @@ class WebRtcCall(val mxCall: MxCall, } private fun release() { + listeners.clear() mxCall.removeListener(this) + timer.stop() + timer.tickListener = null videoCapturer?.stopCapture() videoCapturer?.dispose() videoCapturer = null @@ -700,7 +728,6 @@ class WebRtcCall(val mxCall: MxCall, if (mxCall.state == CallState.Terminated) { return } - mxCall.state = CallState.Terminated // Close tracks ASAP localVideoTrack?.setEnabled(false) localVideoTrack?.setEnabled(false) @@ -708,14 +735,17 @@ class WebRtcCall(val mxCall: MxCall, val cameraManager = context.getSystemService()!! cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback) } - release() + val wasRinging = mxCall.state is CallState.LocalRinging + mxCall.state = CallState.Terminated + GlobalScope.launch(dispatcher) { + release() + } onCallEnded(this) if (originatedByMe) { - // send hang up event - if (mxCall.state is CallState.Connected) { - mxCall.hangUp(reason) - } else { + if (wasRinging) { mxCall.reject() + } else { + mxCall.hangUp(reason) } } } @@ -775,7 +805,7 @@ class WebRtcCall(val mxCall: MxCall, Timber.i("Ignoring colliding negotiate event because we're impolite") return@launch } - val prevOnHold = isLocalOnHold() + val prevOnHold = computeIsLocalOnHold() try { val sdp = SessionDescription(type.asWebRTC(), sdpText) peerConnection.awaitSetRemoteDescription(sdp) @@ -787,8 +817,15 @@ class WebRtcCall(val mxCall: MxCall, } catch (failure: Throwable) { Timber.e(failure, "Failed to complete negotiation") } - val nowOnHold = isLocalOnHold() + val nowOnHold = computeIsLocalOnHold() + wasLocalOnHold = nowOnHold if (prevOnHold != nowOnHold) { + isLocalOnHold = nowOnHold + if (nowOnHold) { + timer.pause() + } else { + timer.resume() + } listeners.forEach { tryOrNull { it.onHoldUnhold() } } @@ -796,9 +833,26 @@ class WebRtcCall(val mxCall: MxCall, } } + private fun formatDuration(duration: Duration): String { + val hours = duration.seconds / 3600 + val minutes = (duration.seconds % 3600) / 60 + val seconds = duration.seconds % 60 + return if (hours > 0) { + String.format("%d:%02d:%02d", hours, minutes, seconds) + } else { + String.format("%02d:%02d", minutes, seconds) + } + } + // MxCall.StateListener override fun onStateUpdate(call: MxCall) { + val state = call.state + if (state is CallState.Connected && state.iceConnectionState == MxPeerConnectionState.CONNECTED) { + timer.resume() + } else { + timer.pause() + } listeners.forEach { tryOrNull { it.onStateUpdate(call) } } 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 92caba579d..aeea0d7d7d 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 @@ -47,7 +47,10 @@ import org.webrtc.DefaultVideoDecoderFactory import org.webrtc.DefaultVideoEncoderFactory import org.webrtc.PeerConnectionFactory import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import javax.inject.Singleton @@ -65,7 +68,7 @@ class WebRtcCallManager @Inject constructor( get() = activeSessionDataSource.currentValue?.orNull() interface CurrentCallListener { - fun onCurrentCallChange(call: WebRtcCall?) + fun onCurrentCallChange(call: WebRtcCall?) {} fun onAudioDevicesChange() {} } @@ -82,7 +85,8 @@ class WebRtcCallManager @Inject constructor( pstnSupportListeners.remove(listener) } - private val currentCallsListeners = emptyList().toMutableList() + private val currentCallsListeners = CopyOnWriteArrayList() + fun addCurrentCallListener(listener: CurrentCallListener) { currentCallsListeners.add(listener) } @@ -129,16 +133,20 @@ class WebRtcCallManager @Inject constructor( isInBackground = true } - var currentCall: WebRtcCall? = null - set(value) { - field = value - currentCallsListeners.forEach { - tryOrNull { it.onCurrentCallChange(value) } - } + /** + * The current call is the call we interacted with whatever his state (connected,resumed, held...) + * As soon as we interact with an other call, it replaces this one and put it on held if not already. + */ + var currentCall: AtomicReference = AtomicReference(null) + private fun AtomicReference.setAndNotify(newValue: WebRtcCall?) { + set(newValue) + currentCallsListeners.forEach { + tryOrNull { it.onCurrentCallChange(newValue) } } + } - private val callsByCallId = HashMap() - private val callsByRoomId = HashMap>() + private val callsByCallId = ConcurrentHashMap() + private val callsByRoomId = ConcurrentHashMap>() fun getCallById(callId: String): WebRtcCall? { return callsByCallId[callId] @@ -148,9 +156,17 @@ class WebRtcCallManager @Inject constructor( return callsByRoomId[roomId] ?: emptyList() } + fun getCurrentCall(): WebRtcCall? { + return currentCall.get() + } + + fun getCalls(): List { + return callsByCallId.values.toList() + } + fun headSetButtonTapped() { Timber.v("## VOIP headSetButtonTapped") - val call = currentCall ?: return + val call = getCurrentCall() ?: return if (call.mxCall.state is CallState.LocalRinging) { // accept call call.acceptIncomingCall() @@ -190,16 +206,26 @@ class WebRtcCallManager @Inject constructor( .createPeerConnectionFactory() } + private fun onCallActive(call: WebRtcCall) { + Timber.v("## VOIP WebRtcPeerConnectionManager onCall active: ${call.mxCall.callId}") + val currentCall = getCurrentCall().takeIf { it != call } + currentCall?.updateRemoteOnHold(onHold = true) + this.currentCall.setAndNotify(call) + } + private fun onCallEnded(call: WebRtcCall) { Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: ${call.mxCall.callId}") - CallService.onNoActiveCall(context) + CallService.onCallTerminated(context, call.callId) callAudioManager.stop() - currentCall = null callsByCallId.remove(call.mxCall.callId) callsByRoomId[call.mxCall.roomId]?.remove(call) + if (getCurrentCall() == call) { + val otherCall = getCalls().lastOrNull() + currentCall.setAndNotify(otherCall) + } // This must be done in this thread executor.execute { - if (currentCall == null) { + if (getCurrentCall() == null) { Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one") peerConnectionFactory?.dispose() peerConnectionFactory = null @@ -210,12 +236,22 @@ class WebRtcCallManager @Inject constructor( fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) { Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall") + if (getCallsByRoomId(signalingRoomId).isNotEmpty()) { + Timber.w("## VOIP you already have a call in this room") + return + } + if (getCurrentCall() != null && getCurrentCall()?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) { + Timber.w("## VOIP cannot start outgoing call") + // Just ignore, maybe we could answer from other session? + return + } executor.execute { createPeerConnectionFactoryIfNeeded() } - + getCurrentCall()?.updateRemoteOnHold(onHold = true) val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return - createWebRtcCall(mxCall) + val webRtcCall = createWebRtcCall(mxCall) + currentCall.setAndNotify(webRtcCall) callAudioManager.startForCall(mxCall) CallService.onOutgoingCallRinging( @@ -228,10 +264,11 @@ class WebRtcCallManager @Inject constructor( override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) { Timber.v("## VOIP onCallIceCandidateReceived for call ${mxCall.callId}") - if (currentCall?.mxCall?.callId != mxCall.callId) return Unit.also { - Timber.w("## VOIP ignore ice candidates from other call") - } - currentCall?.onCallIceCandidateReceived(iceCandidatesContent) + val call = callsByCallId[iceCandidatesContent.callId] + ?: return Unit.also { + Timber.w("onCallIceCandidateReceived for non active call? ${iceCandidatesContent.callId}") + } + call.onCallIceCandidateReceived(iceCandidatesContent) } private fun createWebRtcCall(mxCall: MxCall): WebRtcCall { @@ -246,26 +283,22 @@ class WebRtcCallManager @Inject constructor( peerConnectionFactory }, sessionProvider = { currentSession }, + onCallBecomeActive = this::onCallActive, onCallEnded = this::onCallEnded ) - currentCall = webRtcCall callsByCallId[mxCall.callId] = webRtcCall - callsByRoomId.getOrPut(mxCall.roomId) { ArrayList() } + callsByRoomId.getOrPut(mxCall.roomId) { ArrayList(1) } .add(webRtcCall) return webRtcCall } - fun acceptIncomingCall() { - currentCall?.acceptIncomingCall() - } - - fun endCall(originatedByMe: Boolean = true) { - currentCall?.endCall(originatedByMe) + fun endCallForRoom(roomId: String, originatedByMe: Boolean = true) { + callsByRoomId[roomId]?.forEach { it.endCall(originatedByMe) } } fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) { Timber.v("## VOIP onWiredDeviceEvent $event") - currentCall ?: return + getCurrentCall() ?: return // sometimes we received un-wanted unplugged... callAudioManager.wiredStateChange(event) } @@ -277,8 +310,12 @@ class WebRtcCallManager @Inject constructor( override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) { Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}") - if (currentCall != null) { - Timber.w("## VOIP receiving incoming call while already in call?") + if (getCallsByRoomId(mxCall.roomId).isNotEmpty()) { + Timber.w("## VOIP you already have a call in this room") + return + } + if ((getCurrentCall() != null && getCurrentCall()?.mxCall?.state !is CallState.Connected) || getCalls().size >= 2) { + Timber.w("## VOIP receiving incoming call but cannot handle it") // Just ignore, maybe we could answer from other session? return } @@ -358,12 +395,12 @@ class WebRtcCallManager @Inject constructor( override fun onCallManagedByOtherSession(callId: String) { Timber.v("## VOIP onCallManagedByOtherSession: $callId") - currentCall = null val webRtcCall = callsByCallId.remove(callId) if (webRtcCall != null) { callsByRoomId[webRtcCall.mxCall.roomId]?.remove(webRtcCall) } - CallService.onNoActiveCall(context) + // TODO: handle this properly + CallService.onCallTerminated(context, callId) // did we start background sync? so we should stop it if (isInBackground) { diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt index 6aaa69fbc0..68e169b8c5 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt @@ -32,7 +32,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentContactsBookBinding -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection import im.vector.app.features.userdirectory.UserListAction import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedActionViewModel @@ -44,9 +44,9 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class ContactsBookFragment @Inject constructor( - val contactsBookViewModelFactory: ContactsBookViewModel.Factory, + private val contactsBookViewModelFactory: ContactsBookViewModel.Factory, private val contactsBookController: ContactsBookController -) : VectorBaseFragment(), ContactsBookController.Callback { +) : VectorBaseFragment(), ContactsBookController.Callback, ContactsBookViewModel.Factory { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentContactsBookBinding { return FragmentContactsBookBinding.inflate(inflater, container, false) @@ -59,6 +59,10 @@ class ContactsBookFragment @Inject constructor( private lateinit var sharedActionViewModel: UserListSharedActionViewModel + override fun create(initialState: ContactsBookViewState): ContactsBookViewModel { + return contactsBookViewModelFactory.create(initialState) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java) @@ -128,13 +132,13 @@ class ContactsBookFragment @Inject constructor( override fun onMatrixIdClick(matrixId: String) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(User(matrixId)))) sharedActionViewModel.post(UserListSharedAction.GoBack) } override fun onThreePidClick(threePid: ThreePid) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.ThreePidPendingSelection(threePid))) sharedActionViewModel.post(UserListSharedAction.GoBack) } } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt index 2c4c5d0596..b8cdf08234 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.contactsbook -import androidx.fragment.app.FragmentActivity import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext @@ -31,8 +30,6 @@ import im.vector.app.core.contacts.MappedContact import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.createdirect.CreateDirectRoomActivity -import im.vector.app.features.invite.InviteUsersToRoomActivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback @@ -56,17 +53,11 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: ContactsBookViewState): ContactsBookViewModel? { - return when (viewModelContext) { - is FragmentViewModelContext -> (viewModelContext.fragment() as ContactsBookFragment).contactsBookViewModelFactory.create(state) - is ActivityViewModelContext -> { - when (viewModelContext.activity()) { - is CreateDirectRoomActivity -> viewModelContext.activity().contactsBookViewModelFactory.create(state) - is InviteUsersToRoomActivity -> viewModelContext.activity().contactsBookViewModelFactory.create(state) - else -> error("Wrong activity or fragment") - } - } - else -> error("Wrong activity or fragment") + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt index ce91761fdd..ffc25210e9 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt @@ -17,11 +17,11 @@ package im.vector.app.features.createdirect import im.vector.app.core.platform.VectorViewModelAction -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection sealed class CreateDirectRoomAction : VectorViewModelAction { data class CreateRoomAndInviteSelectedUsers( - val invitees: Set, + val selections: Set, val existingDmRoomId: String? ) : CreateDirectRoomAction() } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index beb7931fd4..4f81841b73 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -45,6 +45,7 @@ import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookViewModel +import im.vector.app.features.contactsbook.ContactsBookViewState import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction @@ -57,7 +58,7 @@ import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import java.net.HttpURLConnection import javax.inject.Inject -class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory { +class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory, CreateDirectRoomViewModel.Factory, ContactsBookViewModel.Factory { private val viewModel: CreateDirectRoomViewModel by viewModel() private lateinit var sharedActionViewModel: UserListSharedActionViewModel @@ -71,9 +72,11 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac injector.inject(this) } - override fun create(initialState: UserListViewState): UserListViewModel { - return userListViewModelFactory.create(initialState) - } + override fun create(initialState: UserListViewState) = userListViewModelFactory.create(initialState) + + override fun create(initialState: CreateDirectRoomViewState) = createDirectRoomViewModelFactory.create(initialState) + + override fun create(initialState: ContactsBookViewState) = contactsBookViewModelFactory.create(initialState) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -143,7 +146,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_create_direct_room) { viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers( - action.invitees, + action.selections, null )) } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt index 94578ed5c7..92a03c5483 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt @@ -29,8 +29,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentQrCodeScannerBinding -import im.vector.app.features.userdirectory.PendingInvitee - +import im.vector.app.features.userdirectory.PendingSelection import me.dm7.barcodescanner.zxing.ZXingScannerView import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser @@ -107,7 +106,7 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null) viewModel.handle( - CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)), existingDm) + CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingSelection.UserPendingSelection(qrInvitee)), existingDm) ) } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index d074c93587..9ac8ed2450 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -18,6 +18,7 @@ package im.vector.app.features.createdirect import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext @@ -27,7 +28,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.raw.RawService @@ -50,8 +51,11 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CreateDirectRoomViewState): CreateDirectRoomViewModel? { - val activity: CreateDirectRoomActivity = (viewModelContext as ActivityViewModelContext).activity() - return activity.createDirectRoomViewModelFactory.create(state) + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") } } @@ -72,11 +76,11 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } } else { // Create the DM - createRoomAndInviteSelectedUsers(action.invitees) + createRoomAndInviteSelectedUsers(action.selections) } } - private fun createRoomAndInviteSelectedUsers(invitees: Set) { + private fun createRoomAndInviteSelectedUsers(selections: Set) { viewModelScope.launch(Dispatchers.IO) { val adminE2EByDefault = rawService.getElementWellknown(session.myUserId) ?.isE2EByDefault() @@ -84,10 +88,10 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted val roomParams = CreateRoomParams() .apply { - invitees.forEach { + selections.forEach { when (it) { - is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId) - is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid) + is PendingSelection.UserPendingSelection -> invitedUserIds.add(it.user.userId) + is PendingSelection.ThreePidPendingSelection -> invite3pids.add(it.threePid) }.exhaustive } setDirectMessage() diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt index 0ce473caaa..feff326cfd 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt @@ -68,7 +68,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment views.ssssKeyUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } - views.ssssKeyReset.views.itemVerificationClickableZone.debouncedClicks { + views.ssssKeyReset.views.bottomSheetActionClickableZone.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt index 8d9157c953..24cb7294d4 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -79,7 +79,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor( } .disposeOnDestroyView() - views.ssssPassphraseReset.views.itemVerificationClickableZone.debouncedClicks { + views.ssssPassphraseReset.views.bottomSheetActionClickableZone.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt index f0a7811666..6c5c259755 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt @@ -44,7 +44,7 @@ class BootstrapConclusionFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - views.bootstrapConclusionContinue.views.itemVerificationClickableZone.debouncedClicks { sharedViewModel.handle(BootstrapActions.Completed) } + views.bootstrapConclusionContinue.views.bottomSheetActionClickableZone.debouncedClicks { sharedViewModel.handle(BootstrapActions.Completed) } } override fun invalidate() = withState(sharedViewModel) { state -> diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt index 82ac15a069..f587f5e58f 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt @@ -52,9 +52,9 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - views.recoverySave.views.itemVerificationClickableZone.debouncedClicks { downloadRecoveryKey() } - views.recoveryCopy.views.itemVerificationClickableZone.debouncedClicks { shareRecoveryKey() } - views.recoveryContinue.views.itemVerificationClickableZone.debouncedClicks { + views.recoverySave.views.bottomSheetActionClickableZone.debouncedClicks { downloadRecoveryKey() } + views.recoveryCopy.views.bottomSheetActionClickableZone.debouncedClicks { shareRecoveryKey() } + views.recoveryContinue.views.bottomSheetActionClickableZone.debouncedClicks { // We do not display the final Fragment anymore // TODO Do some cleanup // sharedViewModel.handle(BootstrapActions.GoToCompleted) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt index 9131ab3c36..8676f1fb6b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt @@ -42,15 +42,15 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor() super.onViewCreated(view, savedInstanceState) // Actions when a key backup exist - views.bootstrapSetupSecureSubmit.views.itemVerificationClickableZone.debouncedClicks { + views.bootstrapSetupSecureSubmit.views.bottomSheetActionClickableZone.debouncedClicks { sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration) } // Actions when there is no key backup - views.bootstrapSetupSecureUseSecurityKey.views.itemVerificationClickableZone.debouncedClicks { + views.bootstrapSetupSecureUseSecurityKey.views.bottomSheetActionClickableZone.debouncedClicks { sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false)) } - views.bootstrapSetupSecureUseSecurityPassphrase.views.itemVerificationClickableZone.debouncedClicks { + views.bootstrapSetupSecureUseSecurityPassphrase.views.bottomSheetActionClickableZone.debouncedClicks { sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true)) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 1e86b4b984..15e982492b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -39,7 +39,6 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.pushers.PushersManager -import im.vector.app.core.utils.toast import im.vector.app.databinding.ActivityHomeBinding import im.vector.app.features.disclaimer.showDisclaimerDialog import im.vector.app.features.matrixto.MatrixToBottomSheet @@ -167,8 +166,8 @@ class HomeActivity : private fun handleIntent(intent: Intent?) { intent?.dataString?.let { deepLink -> val resolvedLink = when { - deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> deepLink - deepLink.startsWith(PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> { + deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> deepLink + deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> { // This is a bit ugly, but for now just convert to matrix.to link for compatibility when { deepLink.startsWith(USER_LINK_PREFIX) -> deepLink.substring(USER_LINK_PREFIX.length) @@ -178,7 +177,7 @@ class HomeActivity : activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(it) } } - else -> null + else -> return@let } permalinkHandler.launch( @@ -191,7 +190,11 @@ class HomeActivity : .observeOn(AndroidSchedulers.mainThread()) .subscribe { isHandled -> if (!isHandled) { - toast(R.string.permalink_malformed) + AlertDialog.Builder(this) + .setTitle(R.string.dialog_title_error) + .setMessage(R.string.permalink_malformed) + .setPositiveButton(R.string.ok, null) + .show() } } .disposeOnDestroy() @@ -411,7 +414,8 @@ class HomeActivity : } } - private const val ROOM_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/" - private const val USER_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/" + private const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://" + private const val ROOM_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/" + private const val USER_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/" } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index d344f1bd86..4c7b7aa991 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -21,7 +21,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat -import androidx.lifecycle.Observer import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -33,11 +32,11 @@ import im.vector.app.core.glide.GlideApp import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.ui.views.ActiveCallView -import im.vector.app.core.ui.views.ActiveCallViewHolder +import im.vector.app.core.ui.views.CurrentCallsView +import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.core.ui.views.KeysBackupBanner import im.vector.app.databinding.FragmentHomeDetailBinding -import im.vector.app.features.call.SharedActiveCallViewModel +import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.list.RoomListFragment @@ -70,7 +69,7 @@ class HomeDetailFragment @Inject constructor( private val vectorPreferences: VectorPreferences ) : VectorBaseFragment(), KeysBackupBanner.Delegate, - ActiveCallView.Callback, + CurrentCallsView.Callback, ServerBackupStatusViewModel.Factory { private val viewModel: HomeDetailViewModel by fragmentViewModel() @@ -78,18 +77,18 @@ class HomeDetailFragment @Inject constructor( private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel() private lateinit var sharedActionViewModel: HomeSharedActionViewModel - private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel + private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding { return FragmentHomeDetailBinding.inflate(inflater, container, false) } - private val activeCallViewHolder = ActiveCallViewHolder() + private val activeCallViewHolder = KnownCallsViewHolder() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) - sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java) + sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java) setupBottomNavigationView() setupToolbar() @@ -127,9 +126,9 @@ class HomeDetailFragment @Inject constructor( } sharedCallActionViewModel - .activeCall - .observe(viewLifecycleOwner, Observer { - activeCallViewHolder.updateCall(it) + .liveKnownCalls + .observe(viewLifecycleOwner, { + activeCallViewHolder.updateCall(callManager.getCurrentCall(), callManager.getCalls()) invalidateOptionsMenu() }) } @@ -336,7 +335,7 @@ class HomeDetailFragment @Inject constructor( } override fun onTapToReturnToCall() { - sharedCallActionViewModel.activeCall.value?.let { call -> + callManager.getCurrentCall()?.let { call -> VectorCallActivity.newIntent( context = requireContext(), callId = call.callId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 13afd9e362..e61aa59dce 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -89,8 +89,8 @@ import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.ui.views.ActiveCallView -import im.vector.app.core.ui.views.ActiveCallViewHolder +import im.vector.app.core.ui.views.CurrentCallsView +import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.JumpToReadMarkerView import im.vector.app.core.ui.views.NotificationAreaView @@ -118,9 +118,10 @@ import im.vector.app.features.attachments.ContactAttachment import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs import im.vector.app.features.attachments.toGroupedContentAttachmentData -import im.vector.app.features.call.SharedActiveCallViewModel +import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.conference.JitsiCallViewModel +import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.command.Command import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.app.features.crypto.util.toImageRes @@ -224,7 +225,8 @@ class RoomDetailFragment @Inject constructor( private val matrixItemColorProvider: MatrixItemColorProvider, private val imageContentRenderer: ImageContentRenderer, private val roomDetailPendingActionStore: RoomDetailPendingActionStore, - private val pillsPostProcessorFactory: PillsPostProcessor.Factory + private val pillsPostProcessorFactory: PillsPostProcessor.Factory, + private val callManager: WebRtcCallManager ) : VectorBaseFragment(), TimelineEventController.Callback, @@ -233,7 +235,7 @@ class RoomDetailFragment @Inject constructor( AttachmentTypeSelectorView.Callback, AttachmentsHelper.Callback, GalleryOrCameraDialogHelper.Listener, - ActiveCallView.Callback { + CurrentCallsView.Callback { companion object { /** @@ -279,7 +281,7 @@ class RoomDetailFragment @Inject constructor( override fun getMenuRes() = R.menu.menu_timeline private lateinit var sharedActionViewModel: MessageSharedActionViewModel - private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel + private lateinit var knownCallsViewModel: SharedKnownCallsViewModel private lateinit var layoutManager: LinearLayoutManager private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager @@ -292,19 +294,19 @@ class RoomDetailFragment @Inject constructor( private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView private var lockSendButton = false - private val activeCallViewHolder = ActiveCallViewHolder() + private val knownCallsViewHolder = KnownCallsViewHolder() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) - sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java) + knownCallsViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java) attachmentsHelper = AttachmentsHelper(requireContext(), this).register() callActionsHandler = StartCallActionsHandler( roomId = roomDetailArgs.roomId, fragment = this, vectorPreferences = vectorPreferences, roomDetailViewModel = roomDetailViewModel, - sharedActiveCallViewModel = sharedCallActionViewModel, + callManager = callManager, startCallActivityResultLauncher = startCallActivityResultLauncher, showDialogWithMessage = ::showDialogWithMessage, onTapToReturnToCall = ::onTapToReturnToCall @@ -332,10 +334,10 @@ class RoomDetailFragment @Inject constructor( } .disposeOnDestroyView() - sharedCallActionViewModel - .activeCall + knownCallsViewModel + .liveKnownCalls .observe(viewLifecycleOwner, { - activeCallViewHolder.updateCall(it) + knownCallsViewHolder.updateCall(callManager.getCurrentCall(), it) invalidateOptionsMenu() }) @@ -613,7 +615,7 @@ class RoomDetailFragment @Inject constructor( } override fun onDestroy() { - activeCallViewHolder.unBind() + knownCallsViewHolder.unBind() roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState) super.onDestroy() } @@ -644,7 +646,7 @@ class RoomDetailFragment @Inject constructor( } private fun setupActiveCallView() { - activeCallViewHolder.bind( + knownCallsViewHolder.bind( views.activeCallPiP, views.activeCallView, views.activeCallPiPWrap, @@ -1920,7 +1922,7 @@ class RoomDetailFragment @Inject constructor( } override fun onTapToReturnToCall() { - sharedCallActionViewModel.activeCall.value?.let { call -> + callManager.getCurrentCall()?.let { call -> VectorCallActivity.newIntent( context = requireContext(), callId = call.callId, 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 22ff994b24..d01b497e55 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 @@ -31,7 +31,6 @@ import im.vector.app.R 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.core.utils.subscribeLogError import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.command.CommandParser @@ -40,7 +39,7 @@ import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler -import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.typing.TypingHelper @@ -113,7 +112,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val rawService: RawService, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val stickerPickerActionHandler: StickerPickerActionHandler, - private val roomSummaryHolder: RoomSummaryHolder, + private val roomSummariesHolder: RoomSummariesHolder, private val typingHelper: TypingHelper, private val callManager: WebRtcCallManager, private val chatEffectManager: ChatEffectManager, @@ -173,7 +172,6 @@ class RoomDetailViewModel @AssistedInject constructor( updateShowDialerOptionState() room.getRoomSummaryLive() room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback()) - room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() // Inform the SDK that the room is displayed session.onRoomDisplayed(initialState.roomId) callManager.addPstnSupportListener(this) @@ -364,7 +362,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleEndCall() { - callManager.endCall() + callManager.endCallForRoom(initialState.roomId) } private fun handleSelectStickerAttachment() { @@ -1338,6 +1336,7 @@ class RoomDetailViewModel @AssistedInject constructor( } } .subscribe { + Timber.v("Unread state: $it") setState { copy(unreadState = it) } } .disposeOnClear() @@ -1390,7 +1389,7 @@ class RoomDetailViewModel @AssistedInject constructor( private fun observeSummaryState() { asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> - roomSummaryHolder.set(summary) + roomSummariesHolder.set(summary) setState { val typingMessage = typingHelper.getTypingMessage(summary.typingUsers) copy(typingMessage = typingMessage) @@ -1445,7 +1444,7 @@ class RoomDetailViewModel @AssistedInject constructor( } override fun onCleared() { - roomSummaryHolder.clear() + roomSummariesHolder.remove(room.roomId) timeline.dispose() timeline.removeAllListeners() if (vectorPreferences.sendTypingNotifs()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt index db48a00dab..30f1ecdc6d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt @@ -27,9 +27,9 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL import im.vector.app.core.utils.checkPermissions import im.vector.app.features.call.DialerChoiceBottomSheet -import im.vector.app.features.call.SharedActiveCallViewModel import im.vector.app.features.call.dialpad.CallDialPadBottomSheet import im.vector.app.features.call.dialpad.DialPadFragment +import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.widgets.model.WidgetType @@ -39,9 +39,9 @@ private const val DIAL_PAD_TAG = "DIAL_PAD_TAG" class StartCallActionsHandler( private val roomId: String, private val fragment: Fragment, + private val callManager: WebRtcCallManager, private val vectorPreferences: VectorPreferences, private val roomDetailViewModel: RoomDetailViewModel, - private val sharedActiveCallViewModel: SharedActiveCallViewModel, private val startCallActivityResultLauncher: ActivityResultLauncher>, private val showDialogWithMessage: (String) -> Unit, private val onTapToReturnToCall: () -> Unit): Restorable { @@ -100,10 +100,10 @@ class StartCallActionsHandler( } } 2 -> { - val activeCall = sharedActiveCallViewModel.activeCall.value - if (activeCall != null) { + val currentCall = callManager.getCurrentCall() + if (currentCall != null) { // resume existing if same room, if not prompt to kill and then restart new call? - if (activeCall.roomId == roomId) { + if (currentCall.roomId == roomId) { onTapToReturnToCall() } // else { 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 dd234266b1..018b8691c0 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 @@ -35,7 +35,6 @@ import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.RoomDetailViewState import im.vector.app.features.home.room.detail.UnreadState import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItemFactory -import im.vector.app.features.home.room.detail.timeline.factory.NoticeItemFactory 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 @@ -77,7 +76,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val mergedHeaderItemFactory: MergedHeaderItemFactory, private val session: Session, private val callManager: WebRtcCallManager, - private val noticeItemFactory: NoticeItemFactory, @TimelineEventControllerHandler private val backgroundHandler: Handler ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor { @@ -104,6 +102,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec // TODO move all callbacks to this? fun onTimelineItemAction(itemAction: RoomDetailAction) + // Introduce ViewModel scoped component (or Hilt?) fun getPreviewUrlRetriever(): PreviewUrlRetriever } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 716fdca2ad..3684f19b61 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -196,7 +196,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.CALL_CANDIDATES, EventType.CALL_HANGUP, EventType.CALL_ANSWER -> { - noticeEventFormatter.format(timelineEvent, room?.roomSummary()) + noticeEventFormatter.format(timelineEvent) } else -> null } ?: "" diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt index 8c972a8684..3992bd7bed 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt @@ -22,7 +22,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory -import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData @@ -43,7 +43,7 @@ class CallItemFactory @Inject constructor( private val messageInformationDataFactory: MessageInformationDataFactory, private val messageItemAttributesFactory: MessageItemAttributesFactory, private val avatarSizeProvider: AvatarSizeProvider, - private val roomSummaryHolder: RoomSummaryHolder, + private val roomSummariesHolder: RoomSummariesHolder, private val callManager: WebRtcCallManager ) { @@ -52,6 +52,7 @@ class CallItemFactory @Inject constructor( callback: TimelineEventController.Callback? ): VectorEpoxyModel<*>? { if (event.root.eventId == null) return null + val roomId = event.roomId val informationData = messageInformationDataFactory.create(event, null) val callSignalingContent = event.getCallSignallingContent() ?: return null val callId = callSignalingContent.callId ?: return null @@ -64,6 +65,7 @@ class CallItemFactory @Inject constructor( return when (event.root.getClearType()) { EventType.CALL_ANSWER -> { createCallTileTimelineItem( + roomId = roomId, callId = callId, callStatus = CallTileTimelineItem.CallStatus.IN_CALL, callKind = callKind, @@ -75,6 +77,7 @@ class CallItemFactory @Inject constructor( } EventType.CALL_INVITE -> { createCallTileTimelineItem( + roomId = roomId, callId = callId, callStatus = CallTileTimelineItem.CallStatus.INVITED, callKind = callKind, @@ -86,6 +89,7 @@ class CallItemFactory @Inject constructor( } EventType.CALL_REJECT -> { createCallTileTimelineItem( + roomId = roomId, callId = callId, callStatus = CallTileTimelineItem.CallStatus.REJECTED, callKind = callKind, @@ -97,6 +101,7 @@ class CallItemFactory @Inject constructor( } EventType.CALL_HANGUP -> { createCallTileTimelineItem( + roomId = roomId, callId = callId, callStatus = CallTileTimelineItem.CallStatus.ENDED, callKind = callKind, @@ -121,6 +126,7 @@ class CallItemFactory @Inject constructor( } private fun createCallTileTimelineItem( + roomId: String, callId: String, callKind: CallTileTimelineItem.CallKind, callStatus: CallTileTimelineItem.CallStatus, @@ -129,7 +135,7 @@ class CallItemFactory @Inject constructor( isStillActive: Boolean, callback: TimelineEventController.Callback? ): CallTileTimelineItem? { - val userOfInterest = roomSummaryHolder.roomSummary?.toMatrixItem() ?: return null + val userOfInterest = roomSummariesHolder.get(roomId)?.toMatrixItem() ?: return null val attributes = messageItemAttributesFactory.create(null, informationData, callback).let { CallTileTimelineItem.Attributes( callId = callId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 23bd041e95..2134645d8d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -22,7 +22,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener -import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.home.room.detail.timeline.helper.canBeMerged import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration import im.vector.app.features.home.room.detail.timeline.helper.prevSameTypeEvents @@ -47,7 +47,7 @@ import javax.inject.Inject class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val avatarRenderer: AvatarRenderer, private val avatarSizeProvider: AvatarSizeProvider, - private val roomSummaryHolder: RoomSummaryHolder) { + private val roomSummariesHolder: RoomSummariesHolder) { private val collapsedEventIds = linkedSetOf() private val mergeItemCollapseStates = HashMap() @@ -77,7 +77,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde } } - private fun isDirectRoom() = roomSummaryHolder.roomSummary?.isDirect.orFalse() + private fun isDirectRoom(roomId: String) = roomSummariesHolder.get(roomId)?.isDirect.orFalse() private fun buildMembershipEventsMergedSummary(currentPosition: Int, items: List, @@ -102,7 +102,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde memberName = mergedEvent.senderInfo.disambiguatedDisplayName, localId = mergedEvent.localId, eventId = mergedEvent.root.eventId ?: "", - isDirectRoom = isDirectRoom() + isDirectRoom = isDirectRoom(event.roomId) ) mergedData.add(data) } @@ -174,7 +174,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde memberName = mergedEvent.senderInfo.disambiguatedDisplayName, localId = mergedEvent.localId, eventId = mergedEvent.root.eventId ?: "", - isDirectRoom = isDirectRoom() + isDirectRoom = isDirectRoom(event.roomId) ) mergedData.add(data) } @@ -191,8 +191,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde collapsedEventIds.removeAll(mergedEventIds) } val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } - val powerLevelsHelper = roomSummaryHolder.roomSummary?.roomId - ?.let { activeSessionHolder.getSafeActiveSession()?.getRoom(it) } + val powerLevelsHelper = activeSessionHolder.getSafeActiveSession()?.getRoom(event.roomId) ?.let { it.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)?.content?.toModel() } ?.let { PowerLevelsHelper(it) } val currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: "" @@ -209,7 +208,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde readReceiptsCallback = callback, callback = callback, currentUserId = currentUserId, - roomSummary = roomSummaryHolder.roomSummary, + roomSummary = roomSummariesHolder.get(event.roomId), canChangeAvatar = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false, canChangeTopic = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false, canChangeName = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index a1e041b98f..2c6176e87e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -38,7 +38,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadSt import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory -import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageBlockCodeItem @@ -105,15 +104,17 @@ class MessageItemFactory @Inject constructor( private val messageItemAttributesFactory: MessageItemAttributesFactory, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, - private val roomSummaryHolder: RoomSummaryHolder, private val defaultItemFactory: DefaultItemFactory, private val noticeItemFactory: NoticeItemFactory, private val avatarSizeProvider: AvatarSizeProvider, private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val session: Session) { + // TODO inject this properly? + private var roomId: String = "" + private val pillsPostProcessor by lazy { - pillsPostProcessorFactory.create(roomSummaryHolder.roomSummary?.roomId) + pillsPostProcessorFactory.create(roomId) } fun create(event: TimelineEvent, @@ -122,8 +123,8 @@ class MessageItemFactory @Inject constructor( callback: TimelineEventController.Callback? ): VectorEpoxyModel<*>? { event.root.eventId ?: return null + roomId = event.roomId val informationData = messageInformationDataFactory.create(event, nextEvent) - if (event.root.isRedacted()) { // message is redacted val attributes = messageItemAttributesFactory.create(null, informationData, callback) @@ -139,7 +140,7 @@ class MessageItemFactory @Inject constructor( || event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE ) { // This is an edit event, we should display it when debugging as a notice event - return noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + return noticeItemFactory.create(event, highlight, callback) } val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback) @@ -155,7 +156,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, callback) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } @@ -229,14 +230,13 @@ class MessageItemFactory @Inject constructor( attributes: AbsMessageItem.Attributes): VerificationRequestItem? { // If this request is not sent by me or sent to me, we should ignore it in timeline val myUserId = session.myUserId - val roomId = roomSummaryHolder.roomSummary?.roomId if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) { return null } val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId val otherUserName = if (informationData.sentByMe) { - session.getRoomMember(messageContent.toUserId, roomId ?: "")?.displayName + session.getRoomMember(messageContent.toUserId, roomId)?.displayName } else { informationData.memberName } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index ec065543f5..cd8c682f39 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -24,7 +24,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvide import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.item.NoticeItem import im.vector.app.features.home.room.detail.timeline.item.NoticeItem_ -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -35,9 +34,8 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv fun create(event: TimelineEvent, highlight: Boolean, - roomSummary: RoomSummary?, callback: TimelineEventController.Callback?): NoticeItem? { - val formattedText = eventFormatter.format(event, roomSummary) ?: return null + val formattedText = eventFormatter.format(event) ?: return null val informationData = informationDataFactory.create(event, null) val attributes = NoticeItem.Attributes( avatarRenderer = avatarRenderer, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt index 25b5fd718b..31adbdb8a6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt @@ -21,7 +21,6 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import im.vector.app.features.home.room.detail.timeline.item.RoomCreateItem_ import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -33,7 +32,6 @@ import javax.inject.Inject class RoomCreateItemFactory @Inject constructor(private val stringProvider: StringProvider, private val userPreferencesProvider: UserPreferencesProvider, private val session: Session, - private val roomSummaryHolder: RoomSummaryHolder, private val noticeItemFactory: NoticeItemFactory) { fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { @@ -54,7 +52,7 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri private fun defaultRendering(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { return if (userPreferencesProvider.shouldShowHiddenEvents()) { - noticeItemFactory.create(event, false, roomSummaryHolder.roomSummary, callback) + noticeItemFactory.create(event, false, callback) } else { null } 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 432736dba0..bd045e166a 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 @@ -20,7 +20,7 @@ import im.vector.app.core.epoxy.EmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber @@ -32,7 +32,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me private val defaultItemFactory: DefaultItemFactory, private val encryptionItemFactory: EncryptionItemFactory, private val roomCreateItemFactory: RoomCreateItemFactory, - private val roomSummaryHolder: RoomSummaryHolder, + private val roomSummariesHolder: RoomSummariesHolder, private val verificationConclusionItemFactory: VerificationItemFactory, private val callItemFactory: CallItemFactory, private val userPreferencesProvider: UserPreferencesProvider) { @@ -63,7 +63,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_WIDGET, EventType.STATE_ROOM_POWER_LEVELS, EventType.REACTION, - EventType.REDACTION -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback) // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) @@ -91,7 +91,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me // TODO These are not filtered out by timeline when encrypted // For now manually ignore if (userPreferencesProvider.shouldShowHiddenEvents()) { - noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + noticeItemFactory.create(event, highlight, callback) } else { null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index 59daf5a0a0..0b623d78f1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -24,7 +24,6 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory -import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_ import org.matrix.android.sdk.api.session.Session @@ -51,7 +50,6 @@ class VerificationItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val noticeItemFactory: NoticeItemFactory, private val userPreferencesProvider: UserPreferencesProvider, - private val roomSummaryHolder: RoomSummaryHolder, private val stringProvider: StringProvider, private val session: Session ) { @@ -153,7 +151,7 @@ class VerificationItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback? ): VectorEpoxyModel<*>? { - if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, callback) return null } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index f4632b0e10..499e27f838 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -23,7 +23,6 @@ import im.vector.app.core.resources.StringProvider import me.gujun.android.span.span import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS @@ -41,7 +40,7 @@ class DisplayableEventFormatter @Inject constructor( private val noticeEventFormatter: NoticeEventFormatter ) { - fun format(timelineEvent: TimelineEvent, appendAuthor: Boolean, roomSummary: RoomSummary?): CharSequence { + fun format(timelineEvent: TimelineEvent, appendAuthor: Boolean): CharSequence { if (timelineEvent.root.isRedacted()) { return noticeEventFormatter.formatRedactedEvent(timelineEvent.root) } @@ -131,7 +130,7 @@ class DisplayableEventFormatter @Inject constructor( } else -> { return span { - text = noticeEventFormatter.format(timelineEvent, roomSummary) ?: "" + text = noticeEventFormatter.format(timelineEvent) ?: "" textStyle = "italic" } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index c725f5b7dc..9b45adbe99 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.format import im.vector.app.ActiveSessionDataSource import im.vector.app.R import im.vector.app.core.resources.StringProvider +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.extensions.appendNl import org.matrix.android.sdk.api.extensions.orFalse @@ -55,6 +56,7 @@ class NoticeEventFormatter @Inject constructor( private val activeSessionDataSource: ActiveSessionDataSource, private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter, private val vectorPreferences: VectorPreferences, + private val roomSummariesHolder: RoomSummariesHolder, private val sp: StringProvider ) { @@ -65,7 +67,8 @@ class NoticeEventFormatter @Inject constructor( private fun RoomSummary?.isDm() = this?.isDirect.orFalse() - fun format(timelineEvent: TimelineEvent, rs: RoomSummary?): CharSequence? { + fun format(timelineEvent: TimelineEvent): CharSequence? { + val rs = roomSummariesHolder.get(timelineEvent.roomId) return when (val type = timelineEvent.root.getClearType()) { EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root, rs) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 8a8bf364e1..802c177197 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -45,7 +45,7 @@ import javax.inject.Inject * This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline */ class MessageInformationDataFactory @Inject constructor(private val session: Session, - private val roomSummaryHolder: RoomSummaryHolder, + private val roomSummariesHolder: RoomSummariesHolder, private val dateFormatter: VectorDateFormatter, private val vectorPreferences: VectorPreferences) { @@ -116,7 +116,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses } private fun getE2EDecoration(event: TimelineEvent): E2EDecoration { - val roomSummary = roomSummaryHolder.roomSummary + val roomSummary = roomSummariesHolder.get(event.roomId) return if ( event.root.sendState == SendState.SYNCED && roomSummary?.isEncrypted.orFalse() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/RoomSummaryHolder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/RoomSummariesHolder.kt similarity index 63% rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/RoomSummaryHolder.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/RoomSummariesHolder.kt index d3ae091733..ac953f91f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/RoomSummaryHolder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/RoomSummariesHolder.kt @@ -16,25 +16,28 @@ package im.vector.app.features.home.room.detail.timeline.helper -import im.vector.app.core.di.ScreenScope import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject +import javax.inject.Singleton /* - This holds an instance of the current room summary. - You should use this in the context of the timeline. + You can use this to share room summary instances within the app. + You should probably use this only in the context of the timeline */ -@ScreenScope -class RoomSummaryHolder @Inject constructor() { +@Singleton +class RoomSummariesHolder @Inject constructor() { - var roomSummary: RoomSummary? = null - private set + private var roomSummaries = HashMap() fun set(roomSummary: RoomSummary) { - this.roomSummary = roomSummary + roomSummaries[roomSummary.roomId] = roomSummary } + fun get(roomId: String) = roomSummaries[roomId] + + fun remove(roomId: String) = roomSummaries.remove(roomId) + fun clear() { - roomSummary = null + roomSummaries.clear() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt index 1983b05ed3..01c7ad3986 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt @@ -57,7 +57,7 @@ class TimelineSettingsFactory @Inject constructor( return map { EventTypeFilter( eventType = it, - stateKey = if (it == EventType.STATE_ROOM_MEMBER && userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null + stateKey = if (it == EventType.STATE_ROOM_MEMBER && !userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt index d5996a65ba..aa864851cd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt @@ -23,7 +23,7 @@ import android.widget.LinearLayout import androidx.core.content.withStyledAttributes import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.databinding.ItemTimelineEventPollResultItemBinding +import im.vector.app.databinding.ViewPollResultLineBinding class PollResultLineView @JvmOverloads constructor( context: Context, @@ -31,7 +31,7 @@ class PollResultLineView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr) { - private val views: ItemTimelineEventPollResultItemBinding + private val views: ViewPollResultLineBinding var label: String? = null set(value) { @@ -60,8 +60,8 @@ class PollResultLineView @JvmOverloads constructor( } init { - inflate(context, R.layout.item_timeline_event_poll_result_item, this) - views = ItemTimelineEventPollResultItemBinding.bind(this) + inflate(context, R.layout.view_poll_result_line, this) + views = ViewPollResultLineBinding.bind(this) orientation = HORIZONTAL context.withStyledAttributes(attrs, R.styleable.PollResultLineView) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 02a277f377..30cb360a9d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.list +import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -36,6 +37,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R +import im.vector.app.core.dialogs.withColoredButton import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.exhaustive @@ -246,19 +248,35 @@ class RoomListFragment @Inject constructor( roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY)) } is RoomListQuickActionsSharedAction.Leave -> { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.room_participants_leave_prompt_title) - .setMessage(R.string.room_participants_leave_prompt_msg) - .setPositiveButton(R.string.leave) { _, _ -> - roomListViewModel.handle(RoomListAction.LeaveRoom(quickAction.roomId)) - } - .setNegativeButton(R.string.cancel, null) - .show() - Unit + promptLeaveRoom(quickAction.roomId) } }.exhaustive } + private fun promptLeaveRoom(roomId: String) { + val isPublicRoom = roomListViewModel.isPublicRoom(roomId) + val message = buildString { + append(getString(R.string.room_participants_leave_prompt_msg)) + if (!isPublicRoom) { + append("\n\n") + append(getString(R.string.room_participants_leave_private_warning)) + } + } + AlertDialog.Builder(requireContext()) + .setTitle(R.string.room_participants_leave_prompt_title) + .setMessage(message) + .setPositiveButton(R.string.leave) { _, _ -> + roomListViewModel.handle(RoomListAction.LeaveRoom(roomId)) + } + .setNegativeButton(R.string.cancel, null) + .show() + .apply { + if (!isPublicRoom) { + withColoredButton(DialogInterface.BUTTON_POSITIVE) + } + } + } + override fun invalidate() = withState(roomListViewModel) { state -> when (state.asyncFilteredRooms) { is Incomplete -> renderLoading() 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 84652506cd..6e5081a31c 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 @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.lang.Exception @@ -78,6 +79,10 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, }.exhaustive } + fun isPublicRoom(roomId: String): Boolean { + return session.getRoom(roomId)?.isPublic().orFalse() + } + // PRIVATE METHODS ***************************************************************************** private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 7bb9940ec4..06cb0172d0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -86,7 +86,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor var latestEventTime: CharSequence = "" val latestEvent = roomSummary.latestPreviewableEvent if (latestEvent != null) { - latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect.not(), roomSummary) + latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect.not()) latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST) } val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers) diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt index cd9df8dc96..be9ad61868 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt @@ -17,8 +17,8 @@ package im.vector.app.features.invite import im.vector.app.core.platform.VectorViewModelAction -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection sealed class InviteUsersToRoomAction : VectorViewModelAction { - data class InviteSelectedUsers(val invitees: Set) : InviteUsersToRoomAction() + data class InviteSelectedUsers(val selections: Set) : InviteUsersToRoomAction() } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 9949255e32..f9f5b2b995 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -39,6 +39,7 @@ import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.toast import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookViewModel +import im.vector.app.features.contactsbook.ContactsBookViewState import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction @@ -53,7 +54,7 @@ import javax.inject.Inject @Parcelize data class InviteUsersToRoomArgs(val roomId: String) : Parcelable -class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory { +class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory, ContactsBookViewModel.Factory, InviteUsersToRoomViewModel.Factory { private val viewModel: InviteUsersToRoomViewModel by viewModel() private lateinit var sharedActionViewModel: UserListSharedActionViewModel @@ -67,9 +68,11 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa injector.inject(this) } - override fun create(initialState: UserListViewState): UserListViewModel { - return userListViewModelFactory.create(initialState) - } + override fun create(initialState: UserListViewState) = userListViewModelFactory.create(initialState) + + override fun create(initialState: ContactsBookViewState) = contactsBookViewModelFactory.create(initialState) + + override fun create(initialState: InviteUsersToRoomViewState) = inviteUsersToRoomViewModelFactory.create(initialState) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -92,7 +95,6 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa } .disposeOnDestroy() if (isFirstCreation()) { - val args: InviteUsersToRoomArgs? = intent.extras?.getParcelable(MvRx.KEY_ARG) addFragment( R.id.container, UserListFragment::class.java, @@ -100,7 +102,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa title = getString(R.string.invite_users_to_room_title), menuResId = R.menu.vector_invite_users_to_room, excludedUserIds = viewModel.getUserIdsOfRoomMembers(), - existingRoomId = args?.roomId + showInviteActions = false ) ) } @@ -110,7 +112,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_invite_users_to_room_invite) { - viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees)) + viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selections)) } } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 21a998d8e2..c0e9ea6d51 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.invite import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted @@ -24,7 +25,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection import io.reactivex.Observable import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.rx.rx @@ -46,37 +47,40 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted @JvmStatic override fun create(viewModelContext: ViewModelContext, state: InviteUsersToRoomViewState): InviteUsersToRoomViewModel? { - val activity: InviteUsersToRoomActivity = (viewModelContext as ActivityViewModelContext).activity() - return activity.inviteUsersToRoomViewModelFactory.create(state) + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") } } override fun handle(action: InviteUsersToRoomAction) { when (action) { - is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.invitees) + is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.selections) } } - private fun inviteUsersToRoom(invitees: Set) { + private fun inviteUsersToRoom(selections: Set) { _viewEvents.post(InviteUsersToRoomViewEvents.Loading) - Observable.fromIterable(invitees).flatMapCompletable { user -> + Observable.fromIterable(selections).flatMapCompletable { user -> when (user) { - is PendingInvitee.UserPendingInvitee -> room.rx().invite(user.user.userId, null) - is PendingInvitee.ThreePidPendingInvitee -> room.rx().invite3pid(user.threePid) + is PendingSelection.UserPendingSelection -> room.rx().invite(user.user.userId, null) + is PendingSelection.ThreePidPendingSelection -> room.rx().invite3pid(user.threePid) } }.subscribe( { - val successMessage = when (invitees.size) { + val successMessage = when (selections.size) { 1 -> stringProvider.getString(R.string.invitation_sent_to_one_user, - invitees.first().getBestName()) + selections.first().getBestName()) 2 -> stringProvider.getString(R.string.invitations_sent_to_two_users, - invitees.first().getBestName(), - invitees.last().getBestName()) + selections.first().getBestName(), + selections.last().getBestName()) else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users, - invitees.size - 1, - invitees.first().getBestName(), - invitees.size - 1) + selections.size - 1, + selections.first().getBestName(), + selections.size - 1) } _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage)) }, diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt index c20f4ddd23..3fc5037ae7 100644 --- a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt @@ -87,7 +87,12 @@ abstract class AbstractSSOLoginFragment : AbstractLoginFragment withState(loginViewModel) { state -> if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { // in this case we can prefetch (not other cases for privacy concerns) - prefetchUrl(state.getSsoUrl(null)) + loginViewModel.getSsoUrl( + redirectUrl = LoginActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + providerId = null + ) + ?.let { prefetchUrl(it) } } } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index 503b6d74a6..803fd38983 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -360,6 +360,9 @@ open class LoginActivity : VectorBaseActivity(), ToolbarCo private const val EXTRA_CONFIG = "EXTRA_CONFIG" + // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string + const val VECTOR_REDIRECT_URL = "element://connect" + fun newIntent(context: Context, loginConfig: LoginConfig?): Intent { return Intent(context, LoginActivity::class.java).apply { putExtra(EXTRA_CONFIG, loginConfig) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index c396e61b1a..3b22e0f206 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -193,7 +193,12 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment if (state.loginMode is LoginMode.Sso) { - openInCustomTab(state.getSsoUrl(null)) + loginViewModel.getSsoUrl( + redirectUrl = LoginActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + providerId = null + ) + ?.let { openInCustomTab(it) } } else { loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp)) } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index 0a6dbcaae2..666bd21add 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -818,4 +818,12 @@ class LoginViewModel @AssistedInject constructor( fun getInitialHomeServerUrl(): String? { return loginConfig?.homeServerUrl } + + fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { + return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId) + } + + fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? { + return authenticationService.getFallbackUrl(forSignIn, deviceId) + } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt index 5254abf1d9..37ac89794f 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt @@ -22,10 +22,6 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.PersistState import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized -import im.vector.app.core.extensions.appendParamToUrl -import org.matrix.android.sdk.api.auth.MSC2858_SSO_REDIRECT_PATH -import org.matrix.android.sdk.api.auth.SSO_REDIRECT_PATH -import org.matrix.android.sdk.api.auth.SSO_REDIRECT_URL_PARAM data class LoginViewState( val asyncLoginAction: Async = Uninitialized, @@ -69,27 +65,4 @@ data class LoginViewState( fun isUserLogged(): Boolean { return asyncLoginAction is Success } - - fun getSsoUrl(providerId: String?): String { - return buildString { - append(homeServerUrl?.trim { it == '/' }) - if (providerId != null) { - append(MSC2858_SSO_REDIRECT_PATH) - append("/$providerId") - } else { - append(SSO_REDIRECT_PATH) - } - // Set a redirect url we will intercept later - appendParamToUrl(SSO_REDIRECT_URL_PARAM, VECTOR_REDIRECT_URL) - deviceId?.takeIf { it.isNotBlank() }?.let { - // But https://github.com/matrix-org/synapse/issues/5755 - appendParamToUrl("device_id", it) - } - } - } - - companion object { - // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string - private const val VECTOR_REDIRECT_URL = "element://connect" - } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt index acf4f706c5..4b03c93321 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt @@ -33,14 +33,11 @@ import android.webkit.WebViewClient import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.activityViewModel import im.vector.app.R -import im.vector.app.core.extensions.appendParamToUrl import im.vector.app.core.utils.AssetReader import im.vector.app.databinding.FragmentLoginWebBinding import im.vector.app.features.signout.soft.SoftLogoutAction import im.vector.app.features.signout.soft.SoftLogoutViewModel -import org.matrix.android.sdk.api.auth.LOGIN_FALLBACK_PATH -import org.matrix.android.sdk.api.auth.REGISTER_FALLBACK_PATH import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.di.MoshiProvider import timber.log.Timber @@ -119,19 +116,7 @@ class LoginWebFragment @Inject constructor( } private fun launchWebView(state: LoginViewState) { - val url = buildString { - append(state.homeServerUrl?.trim { it == '/' }) - if (state.signMode == SignMode.SignIn) { - append(LOGIN_FALLBACK_PATH) - state.deviceId?.takeIf { it.isNotBlank() }?.let { - // But https://github.com/matrix-org/synapse/issues/5755 - appendParamToUrl("device_id", it) - } - } else { - // MODE_REGISTER - append(REGISTER_FALLBACK_PATH) - } - } + val url = loginViewModel.getFallbackUrl(state.signMode == SignMode.SignIn, state.deviceId) ?: return views.loginWebWebView.loadUrl(url) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 0a6197e424..991f776a45 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -34,6 +34,7 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.VectorJitsiActivity +import im.vector.app.features.call.transfer.CallTransferActivity import im.vector.app.features.createdirect.CreateDirectRoomActivity import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity @@ -344,6 +345,11 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } + override fun openCallTransfer(context: Context, callId: String) { + val intent = CallTransferActivity.newIntent(context, callId) + context.startActivity(intent) + } + private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) { if (buildTask) { val stackBuilder = TaskStackBuilder.create(context) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 504fccb63a..bc13e99bbf 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -113,4 +113,6 @@ interface Navigator { options: ((MutableList>) -> Unit)?) fun openSearch(context: Context, roomId: String) + + fun openCallTransfer(context: Context, callId: String) } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 9c2dc9b26d..c1bb1dde36 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -91,7 +91,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St if (room == null) { Timber.e("## Unable to resolve room for eventId [$event]") // Ok room is not known in store, but we can still display something - val body = displayableEventFormatter.format(event, false, null) + val body = displayableEventFormatter.format(event, false) val roomName = stringProvider.getString(R.string.notification_unknown_room_name) val senderDisplayName = event.senderInfo.disambiguatedDisplayName @@ -124,7 +124,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St } } - val body = displayableEventFormatter.format(event, false, room.roomSummary()).toString() + val body = displayableEventFormatter.format(event, false).toString() val roomName = room.roomSummary()?.displayName ?: "" val senderDisplayName = event.senderInfo.disambiguatedDisplayName diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 74a93fceda..d1e52b2a78 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -297,12 +297,6 @@ class NotificationUtils @Inject constructor(private val context: Context, .setLights(accentColor, 500, 500) .setOngoing(true) - // Compat: Display the incoming call notification on the lock screen - builder.priority = NotificationCompat.PRIORITY_HIGH - - // -// val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT) - val contentIntent = VectorCallActivity.newIntent( context = context, mxCall = mxCall, @@ -340,9 +334,11 @@ class NotificationUtils @Inject constructor(private val context: Context, answerCallPendingIntent ) ) - - builder.setFullScreenIntent(contentPendingIntent, true) - + if (fromBg) { + // Compat: Display the incoming call notification on the lock screen + builder.priority = NotificationCompat.PRIORITY_HIGH + builder.setFullScreenIntent(contentPendingIntent, true) + } return builder.build() } @@ -393,9 +389,8 @@ class NotificationUtils @Inject constructor(private val context: Context, */ @SuppressLint("NewApi") fun buildPendingCallNotification(mxCall: MxCall, - title: String, - fromBg: Boolean = false): Notification { - val builder = NotificationCompat.Builder(context, if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID) + title: String): Notification { + val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setContentTitle(ensureTitleNotEmpty(title)) .apply { if (mxCall.isVideoCall) { @@ -407,11 +402,6 @@ class NotificationUtils @Inject constructor(private val context: Context, .setSmallIcon(R.drawable.incoming_call_notification_transparent) .setCategory(NotificationCompat.CATEGORY_CALL) - if (fromBg) { - builder.priority = NotificationCompat.PRIORITY_LOW - builder.setOngoing(true) - } - val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId) builder.addAction( @@ -450,6 +440,7 @@ class NotificationUtils @Inject constructor(private val context: Context, fun buildCallEndedNotification(): Notification { return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setContentTitle(stringProvider.getString(R.string.call_ended)) + .setTimeoutAfter(2000) .setSmallIcon(R.drawable.ic_material_call_end_grey) .setCategory(NotificationCompat.CATEGORY_CALL) .build() diff --git a/vector/src/main/java/im/vector/app/features/popup/IncomingCallAlert.kt b/vector/src/main/java/im/vector/app/features/popup/IncomingCallAlert.kt index 64e729e54d..f80d6d1bbd 100644 --- a/vector/src/main/java/im/vector/app/features/popup/IncomingCallAlert.kt +++ b/vector/src/main/java/im/vector/app/features/popup/IncomingCallAlert.kt @@ -21,6 +21,8 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import im.vector.app.R +import im.vector.app.core.extensions.setLeftDrawable +import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.util.MatrixItem @@ -42,18 +44,24 @@ class IncomingCallAlert(uid: String, : VectorAlert.ViewBinder { override fun bind(view: View) { - val callKind = if (isVideoCall) { - R.string.action_video_call + val (callKindText, callKindIcon) = if (isVideoCall) { + Pair(R.string.action_video_call, R.drawable.ic_call_video_small) } else { - R.string.action_voice_call + Pair(R.string.action_voice_call, R.drawable.ic_call_audio_small) + } + view.findViewById(R.id.incomingCallKindView).apply { + setText(callKindText) + setLeftDrawable(callKindIcon) } - view.findViewById(R.id.incomingCallKindView).setText(callKind) view.findViewById(R.id.incomingCallNameView).text = matrixItem?.getBestName() view.findViewById(R.id.incomingCallAvatar)?.let { imageView -> - matrixItem?.let { avatarRenderer.render(it, imageView) } + matrixItem?.let { avatarRenderer.render(it, imageView, GlideApp.with(view.context.applicationContext)) } } - view.findViewById(R.id.incomingCallAcceptView).setOnClickListener { - onAccept() + view.findViewById(R.id.incomingCallAcceptView).apply { + setOnClickListener { + onAccept() + } + setImageResource(callKindIcon) } view.findViewById(R.id.incomingCallRejectView).setOnClickListener { onReject() diff --git a/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt b/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt index 2cd9ab59ac..ee6728f969 100644 --- a/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt +++ b/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt @@ -21,6 +21,7 @@ import android.view.View import android.widget.ImageView import androidx.annotation.DrawableRes import im.vector.app.R +import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.util.MatrixItem @@ -43,7 +44,7 @@ class VerificationVectorAlert(uid: String, override fun bind(view: View) { view.findViewById(R.id.ivUserAvatar)?.let { imageView -> - matrixItem?.let { avatarRenderer.render(it, imageView) } + matrixItem?.let { avatarRenderer.render(it, imageView, GlideApp.with(view.context.applicationContext)) } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index 891d15d04f..6b988df94d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -26,6 +26,8 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.features.home.ShortcutCreator +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -53,6 +55,7 @@ class RoomProfileController @Inject constructor( fun onSettingsClicked() fun onLeaveRoomClicked() fun onRoomIdClicked() + fun onUrlInTopicLongClicked(url: String) } override fun buildModels(data: RoomProfileViewState?) { @@ -71,6 +74,16 @@ class RoomProfileController @Inject constructor( id("topic") content(it) maxLines(2) + movementMethod(createLinkMovementMethod(object : TimelineEventController.UrlClickCallback { + override fun onUrlClicked(url: String, title: String): Boolean { + return false + } + + override fun onUrlLongClicked(url: String): Boolean { + callback?.onUrlInTopicLongClicked(url) + return true + } + })) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 473c1d4324..58fd78e26a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile +import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -34,6 +35,7 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.animations.AppBarStateChangeListener import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener +import im.vector.app.core.dialogs.withColoredButton import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.copyOnLongClick @@ -247,20 +249,37 @@ class RoomProfileFragment @Inject constructor( } override fun onLeaveRoomClicked() { + val isPublicRoom = roomProfileViewModel.isPublicRoom() + val message = buildString { + append(getString(R.string.room_participants_leave_prompt_msg)) + if (!isPublicRoom) { + append("\n\n") + append(getString(R.string.room_participants_leave_private_warning)) + } + } AlertDialog.Builder(requireContext()) .setTitle(R.string.room_participants_leave_prompt_title) - .setMessage(R.string.room_participants_leave_prompt_msg) + .setMessage(message) .setPositiveButton(R.string.leave) { _, _ -> roomProfileViewModel.handle(RoomProfileAction.LeaveRoom) } .setNegativeButton(R.string.cancel, null) .show() + .apply { + if (!isPublicRoom) { + withColoredButton(DialogInterface.BUTTON_POSITIVE) + } + } } override fun onRoomIdClicked() { copyToClipboard(requireContext(), roomProfileArgs.roomId) } + override fun onUrlInTopicLongClicked(url: String) { + copyToClipboard(requireContext(), url, true) + } + private fun onShareRoomProfile(permalink: String) { startSharePlainTextIntent( fragment = this, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index ec772ffcaa..cfdf1d9c0b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.rx.RxRoom import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -109,6 +110,10 @@ class RoomProfileViewModel @AssistedInject constructor( }.exhaustive } + fun isPublicRoom(): Boolean { + return room.isPublic() + } + private fun handleEnableEncryption() { postLoading(true) diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt index bba6b9c253..0856ac4b35 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt @@ -18,6 +18,8 @@ package im.vector.app.features.themes import android.app.Activity import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources import android.graphics.drawable.Drawable import android.util.TypedValue import android.view.Menu @@ -39,10 +41,14 @@ object ThemeUtils { const val APPLICATION_THEME_KEY = "APPLICATION_THEME_KEY" // the theme possible values + private const val SYSTEM_THEME_VALUE = "system" private const val THEME_DARK_VALUE = "dark" private const val THEME_LIGHT_VALUE = "light" private const val THEME_BLACK_VALUE = "black" + // The default theme + private const val DEFAULT_THEME = SYSTEM_THEME_VALUE + private var currentTheme = AtomicReference(null) private val mColorByAttr = HashMap() @@ -54,13 +60,12 @@ object ThemeUtils { } /** - * @return true if current theme is Light or Status + * @return true if current theme is Light or current theme is System and system theme is light */ fun isLightTheme(context: Context): Boolean { - return when (getApplicationTheme(context)) { - THEME_LIGHT_VALUE -> true - else -> false - } + val theme = getApplicationTheme(context) + return theme == THEME_LIGHT_VALUE + || (theme == SYSTEM_THEME_VALUE && !isSystemDarkTheme(context.resources)) } /** @@ -73,11 +78,11 @@ object ThemeUtils { val currentTheme = this.currentTheme.get() return if (currentTheme == null) { val prefs = DefaultSharedPreferences.getInstance(context) - var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) ?: THEME_LIGHT_VALUE + var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, DEFAULT_THEME) ?: DEFAULT_THEME if (themeFromPref == "status") { - // Migrate to light theme, which is the closest theme - themeFromPref = THEME_LIGHT_VALUE - prefs.edit { putString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) } + // Migrate to the default theme + themeFromPref = DEFAULT_THEME + prefs.edit { putString(APPLICATION_THEME_KEY, DEFAULT_THEME) } } this.currentTheme.set(themeFromPref) themeFromPref @@ -86,6 +91,13 @@ object ThemeUtils { } } + /** + * @return true if system theme is dark + */ + private fun isSystemDarkTheme(resources: Resources): Boolean { + return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + } + /** * Update the application theme * @@ -93,11 +105,14 @@ object ThemeUtils { */ fun setApplicationTheme(context: Context, aTheme: String) { currentTheme.set(aTheme) - when (aTheme) { - THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark) - THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black) - else -> context.setTheme(R.style.AppTheme_Light) - } + context.setTheme( + when (aTheme) { + SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.AppTheme_Dark else R.style.AppTheme_Light + THEME_DARK_VALUE -> R.style.AppTheme_Dark + THEME_BLACK_VALUE -> R.style.AppTheme_Black + else -> R.style.AppTheme_Light + } + ) // Clear the cache mColorByAttr.clear() @@ -110,6 +125,7 @@ object ThemeUtils { */ fun setActivityTheme(activity: Activity, otherThemes: ActivityOtherThemes) { when (getApplicationTheme(activity)) { + SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(activity.resources)) activity.setTheme(otherThemes.dark) THEME_DARK_VALUE -> activity.setTheme(otherThemes.dark) THEME_BLACK_VALUE -> activity.setTheme(otherThemes.black) } @@ -117,40 +133,6 @@ object ThemeUtils { mColorByAttr.clear() } - /** - * Set the TabLayout colors. - * It seems that there is no proper way to manage it with the manifest file. - * - * @param activity the activity - * @param layout the layout - */ - /* - fun setTabLayoutTheme(activity: Activity, layout: TabLayout) { - if (activity is VectorGroupDetailsActivity) { - val textColor: Int - val underlineColor: Int - val backgroundColor: Int - - if (TextUtils.equals(getApplicationTheme(activity), THEME_LIGHT_VALUE)) { - textColor = ContextCompat.getColor(activity, android.R.color.white) - underlineColor = textColor - backgroundColor = ContextCompat.getColor(activity, R.color.tab_groups) - } else if (TextUtils.equals(getApplicationTheme(activity), THEME_STATUS_VALUE)) { - textColor = ContextCompat.getColor(activity, android.R.color.white) - underlineColor = textColor - backgroundColor = getColor(activity, R.attr.colorPrimary) - } else { - textColor = ContextCompat.getColor(activity, R.color.tab_groups) - underlineColor = textColor - backgroundColor = getColor(activity, R.attr.colorPrimary) - } - - layout.setTabTextColors(textColor, textColor) - layout.setSelectedTabIndicatorColor(underlineColor) - layout.setBackgroundColor(backgroundColor) - } - } */ - /** * Translates color attributes to colors * diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/PendingInvitee.kt b/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt similarity index 73% rename from vector/src/main/java/im/vector/app/features/userdirectory/PendingInvitee.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt index f7213497fa..57f950b2c8 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/PendingInvitee.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt @@ -19,14 +19,14 @@ package im.vector.app.features.userdirectory import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.user.model.User -sealed class PendingInvitee { - data class UserPendingInvitee(val user: User) : PendingInvitee() - data class ThreePidPendingInvitee(val threePid: ThreePid) : PendingInvitee() +sealed class PendingSelection { + data class UserPendingSelection(val user: User) : PendingSelection() + data class ThreePidPendingSelection(val threePid: ThreePid) : PendingSelection() fun getBestName(): String { return when (this) { - is UserPendingInvitee -> user.getBestName() - is ThreePidPendingInvitee -> threePid.value + is UserPendingSelection -> user.getBestName() + is ThreePidPendingSelection -> threePid.value } } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt index 0c2c4b1f4b..7835232b09 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class UserListAction : VectorViewModelAction { data class SearchUsers(val value: String) : UserListAction() object ClearSearchUsers : UserListAction() - data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction() - data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction() + data class AddPendingSelection(val pendingSelection: PendingSelection) : UserListAction() + data class RemovePendingSelection(val pendingSelection: PendingSelection) : UserListAction() object ComputeMatrixToLinkForSharing : UserListAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt index 3e1523d0cc..331329ae61 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -54,10 +54,7 @@ class UserListController @Inject constructor(private val session: Session, // Build generic items if (currentState.searchTerm.isBlank()) { - // For now we remove this option if in invite to existing room flow (and not create DM) - if (currentState.pendingInvitees.isEmpty() - // For now we remove this option if in invite to existing room flow (and not create DM) - && currentState.existingRoomId == null) { + if (currentState.showInviteActions()) { actionItem { id(R.drawable.ic_share) title(stringProvider.getString(R.string.invite_friends)) @@ -75,9 +72,7 @@ class UserListController @Inject constructor(private val session: Session, callback?.onContactBookClick() }) } - if (currentState.pendingInvitees.isEmpty() - // For now we remove this option if in invite to existing room flow (and not create DM) - && currentState.existingRoomId == null) { + if (currentState.showInviteActions()) { actionItem { id(R.drawable.ic_qr_code_add) title(stringProvider.getString(R.string.qr_code)) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index d06030c301..96f459cbbf 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -67,18 +67,22 @@ class UserListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java) - views.userListTitle.text = args.title - vectorBaseActivity.setSupportActionBar(views.userListToolbar) - + if (args.showToolbar) { + views.userListTitle.text = args.title + vectorBaseActivity.setSupportActionBar(views.userListToolbar) + setupCloseView() + views.userListToolbar.isVisible = true + } else { + views.userListToolbar.isVisible = false + } setupRecyclerView() setupSearchView() - setupCloseView() homeServerCapabilitiesViewModel.subscribe { views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault } - viewModel.selectSubscribe(this, UserListViewState::pendingInvitees) { + viewModel.selectSubscribe(this, UserListViewState::pendingSelections) { renderSelectedUsers(it) } @@ -105,7 +109,7 @@ class UserListFragment @Inject constructor( override fun onPrepareOptionsMenu(menu: Menu) { withState(viewModel) { - val showMenuItem = it.pendingInvitees.isNotEmpty() + val showMenuItem = it.pendingSelections.isNotEmpty() menu.forEach { menuItem -> menuItem.isVisible = showMenuItem } @@ -114,7 +118,7 @@ class UserListFragment @Inject constructor( } override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { - sharedActionViewModel.post(UserListSharedAction.OnMenuItemSelected(item.itemId, it.pendingInvitees)) + sharedActionViewModel.post(UserListSharedAction.OnMenuItemSelected(item.itemId, it.pendingSelections)) return@withState true } @@ -156,14 +160,14 @@ class UserListFragment @Inject constructor( userListController.setData(it) } - private fun renderSelectedUsers(invitees: Set) { + private fun renderSelectedUsers(selections: Set) { invalidateOptionsMenu() val currentNumberOfChips = views.chipGroup.childCount - val newNumberOfChips = invitees.size + val newNumberOfChips = selections.size views.chipGroup.removeAllViews() - invitees.forEach { addChipToGroup(it) } + selections.forEach { addChipToGroup(it) } // Scroll to the bottom when adding chips. When removing chips, do not scroll if (newNumberOfChips >= currentNumberOfChips) { @@ -173,20 +177,22 @@ class UserListFragment @Inject constructor( } } - private fun addChipToGroup(pendingInvitee: PendingInvitee) { + private fun addChipToGroup(pendingSelection: PendingSelection) { val chip = Chip(requireContext()) chip.setChipBackgroundColorResource(android.R.color.transparent) chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat() - chip.text = pendingInvitee.getBestName() + chip.text = pendingSelection.getBestName() chip.isClickable = true chip.isCheckable = false chip.isCloseIconVisible = true views.chipGroup.addView(chip) chip.setOnCloseIconClickListener { - viewModel.handle(UserListAction.RemovePendingInvitee(pendingInvitee)) + viewModel.handle(UserListAction.RemovePendingSelection(pendingSelection)) } } + fun getCurrentState() = withState(viewModel) { it } + override fun onInviteFriendClick() { viewModel.handle(UserListAction.ComputeMatrixToLinkForSharing) } @@ -197,17 +203,17 @@ class UserListFragment @Inject constructor( override fun onItemClick(user: User) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(user))) } override fun onMatrixIdClick(matrixId: String) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(User(matrixId)))) } override fun onThreePidClick(threePid: ThreePid) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.ThreePidPendingSelection(threePid))) } override fun onUseQRCode() { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt index 02fd13b39b..dce1f46b2f 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt @@ -24,5 +24,7 @@ data class UserListFragmentArgs( val title: String, val menuResId: Int, val excludedUserIds: Set? = null, - val existingRoomId: String? = null + val singleSelection: Boolean = false, + val showInviteActions: Boolean = true, + val showToolbar: Boolean = true ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt index b2cdee3e63..fca771793b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorSharedAction sealed class UserListSharedAction : VectorSharedAction { object Close : UserListSharedAction() object GoBack : UserListSharedAction() - data class OnMenuItemSelected(val itemId: Int, val invitees: Set) : UserListSharedAction() + data class OnMenuItemSelected(val itemId: Int, val selections: Set) : UserListSharedAction() object OpenPhoneBook : UserListSharedAction() object AddByQrCode : UserListSharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index f8eabbaed0..322f5eef21 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -68,21 +68,15 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User } init { - setState { - copy( - myUserId = session.myUserId, - existingRoomId = initialState.existingRoomId - ) - } observeUsers() } override fun handle(action: UserListAction) { when (action) { - is UserListAction.SearchUsers -> handleSearchUsers(action.value) - is UserListAction.ClearSearchUsers -> handleClearSearchUsers() - is UserListAction.SelectPendingInvitee -> handleSelectUser(action) - is UserListAction.RemovePendingInvitee -> handleRemoveSelectedUser(action) + is UserListAction.SearchUsers -> handleSearchUsers(action.value) + is UserListAction.ClearSearchUsers -> handleClearSearchUsers() + is UserListAction.AddPendingSelection -> handleSelectUser(action) + is UserListAction.RemovePendingSelection -> handleRemoveSelectedUser(action) UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink() }.exhaustive } @@ -168,13 +162,13 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User .disposeOnClear() } - private fun handleSelectUser(action: UserListAction.SelectPendingInvitee) = withState { state -> - val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee) - setState { copy(pendingInvitees = selectedUsers) } + private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state -> + val selections = state.pendingSelections.toggle(action.pendingSelection, singleElement = state.singleSelection) + setState { copy(pendingSelections = selections) } } - private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingInvitee) = withState { state -> - val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee) - setState { copy(pendingInvitees = selectedUsers) } + private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingSelection) = withState { state -> + val selections = state.pendingSelections.minus(action.pendingSelection) + setState { copy(pendingSelections = selections) } } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt index 69135f912d..60ba3a17da 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt @@ -28,24 +28,27 @@ data class UserListViewState( val knownUsers: Async> = Uninitialized, val directoryUsers: Async> = Uninitialized, val filteredMappedContacts: List = emptyList(), - val pendingInvitees: Set = emptySet(), - val createAndInviteState: Async = Uninitialized, + val pendingSelections: Set = emptySet(), val searchTerm: String = "", - val myUserId: String = "", - val existingRoomId: String? = null + val singleSelection: Boolean, + private val showInviteActions: Boolean ) : MvRxState { constructor(args: UserListFragmentArgs) : this( - existingRoomId = args.existingRoomId + excludedUserIds = args.excludedUserIds, + singleSelection = args.singleSelection, + showInviteActions = args.showInviteActions ) fun getSelectedMatrixId(): List { - return pendingInvitees + return pendingSelections .mapNotNull { when (it) { - is PendingInvitee.UserPendingInvitee -> it.user.userId - is PendingInvitee.ThreePidPendingInvitee -> null + is PendingSelection.UserPendingSelection -> it.user.userId + is PendingSelection.ThreePidPendingSelection -> null } } } + + fun showInviteActions() = showInviteActions && pendingSelections.isEmpty() } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt index 00df261095..61bef29d54 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt @@ -20,19 +20,19 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.util.AttributeSet -import android.widget.LinearLayout +import android.widget.FrameLayout import androidx.core.content.withStyledAttributes import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.databinding.ItemSignoutActionBinding +import im.vector.app.databinding.ViewSignOutBottomSheetActionButtonBinding import im.vector.app.features.themes.ThemeUtils class SignOutBottomSheetActionButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr) { +) : FrameLayout(context, attrs, defStyleAttr) { - private val views: ItemSignoutActionBinding + private val views: ViewSignOutBottomSheetActionButtonBinding var action: (() -> Unit)? = null @@ -67,8 +67,8 @@ class SignOutBottomSheetActionButton @JvmOverloads constructor( } init { - inflate(context, R.layout.item_signout_action, this) - views = ItemSignoutActionBinding.bind(this) + inflate(context, R.layout.view_sign_out_bottom_sheet_action_button, this) + views = ViewSignOutBottomSheetActionButtonBinding.bind(this) context.withStyledAttributes(attrs, R.styleable.SignOutBottomSheetActionButton) { title = getString(R.styleable.SignOutBottomSheetActionButton_actionTitle) ?: "" diff --git a/vector/src/main/res/drawable/ic_call_transfer.xml b/vector/src/main/res/drawable/ic_call_transfer.xml new file mode 100644 index 0000000000..b2c28d6ba0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_call_transfer.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/vector/src/main/res/layout/activity_call.xml b/vector/src/main/res/layout/activity_call.xml index b82915d383..45be67ae8e 100644 --- a/vector/src/main/res/layout/activity_call.xml +++ b/vector/src/main/res/layout/activity_call.xml @@ -14,10 +14,10 @@ + android:layout_height="match_parent" + android:scaleType="centerCrop" + tools:src="@tools:sample/avatars" /> + + + + + + + + + app:layout_constraintTop_toTopOf="@id/otherMemberAvatar" /> + + + + + + + + + + + + + + +