Merge branch 'develop' into feature/bca/devtools

This commit is contained in:
Benoit Marty 2021-02-15 16:00:18 +01:00
commit cf0b4d2581
296 changed files with 8962 additions and 3857 deletions

View file

@ -1,20 +1,23 @@
Changes in Element 1.0.18 (2020-XX-XX)
Changes in Element 1.0.18 (2021-XX-XX)
===================================================
Features ✨:
-
- VoIP : support for VoIP V1 protocol, transfer call and dial-pad
Improvements 🙌:
-
- VoIP : new tiles in timeline
- Improve room profile UX
- Upgrade Jitsi library from 2.9.3 to 3.1.0
Bugfix 🐛:
-
- VoIP : fix audio devices output
- Fix crash after initial sync on Dendrite
Translations 🗣:
-
SDK API changes ⚠️:
-
-
Build 🧱:
-
@ -24,8 +27,9 @@ Test:
Other changes:
- New Dev Tools panel for developers
- Fix typos in CHANGES.md (#2811)
Changes in Element 1.0.17 (2020-02-09)
Changes in Element 1.0.17 (2021-02-09)
===================================================
Improvements 🙌:
@ -47,13 +51,13 @@ Build 🧱:
Other changes:
- Change app name from "Element (Riot.im)" to "Element"
Changes in Element 1.0.16 (2020-02-04)
Changes in Element 1.0.16 (2021-02-04)
===================================================
Bugfix 🐛:
- Fix crash on API < 30 and light theme (#2774)
Changes in Element 1.0.15 (2020-02-03)
Changes in Element 1.0.15 (2021-02-03)
===================================================
Features ✨:
@ -84,7 +88,7 @@ Build 🧱:
Other changes:
- Update Dagger to 2.31 version so we can use the embedded AssistedInject feature
Changes in Element 1.0.14 (2020-01-15)
Changes in Element 1.0.14 (2021-01-15)
===================================================
Features ✨:
@ -1196,7 +1200,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
=======================================================
Changes in Element 1.X.X (2020-XX-XX)
Changes in Element 1.X.X (2021-XX-XX)
===================================================
Features ✨:

View file

@ -58,9 +58,9 @@ allprojects {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
// Jitsi repo
maven {
url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-2.9.3"
url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
// Note: to test Jitsi release you can use a local file like this:
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-2.9.3"
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
}
google()
jcenter()

View file

@ -18,7 +18,7 @@ The generated maven repository is then host in the project https://github.com/ve
Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`.
Currently we are building the version with the tag `android-sdk-2.9.3`.
Currently we are building the version with the tag `android-sdk-3.1.0`.
### Run the build script
@ -35,21 +35,21 @@ It will build the Jitsi Meet Android library and put every generated files in th
- Update the file `./build.gradle` to use the previously created local Maven repository. Currently we have this line:
```groovy
url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-2.9.3"
url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
```
You can uncomment and update the line starting with `// url "file://...` and comment the line starting with `url`, to test the library using the locally generated Maven repository.
- Update the dependency of the WebRTC library in the file `./matrix-sdk-android/build.gradle`. Currently we have this line:
```groovy
implementation('com.facebook.react:react-native-webrtc:1.84.0-jitsi-5112273@aar')
```
- Update the dependency of the Jitsi Meet library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
implementation('org.jitsi.react:jitsi-meet-sdk:2.9.3') { transitive = true }
implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0')
```
- Update the dependency of the WebRTC library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
implementation('com.facebook.react:react-native-webrtc:1.87.3-jitsi-6624067@aar')
```
- Perform a gradle sync and build the project
@ -74,9 +74,9 @@ If all the tests are passed, you can export the generated Jitsi library to our M
- Update the file `./build.gradle` to use the previously created Maven repository. Currently we have this line:
```groovy
url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-2.9.3"
url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
```
- Build the project and perform the sanity tests again.
- Update the file `/CANGES.md` to notify about the library upgrade, and create a regular PR for project Element Android.
- Update the file `/CHANGES.md` to notify about the library upgrade, and create a regular PR for project Element Android.

View file

@ -1,2 +1,2 @@
Canvis principals d'aquesta versió: previsualització d'URL, nou teclat d'emoticones, noves funcions de configuració de les sales i neu pel Nadal!
Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Canvis principals d'aquesta versió: modificació dels permisos de sala, tema clar/fosc automàtic, correcció d'errors.
Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Canvis principals d'aquesta versió: inici de sessió amb xarxes socials.
Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Canvis principals d'aquesta versió: inici de sessió amb xarxes socials.
Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.0.15 i https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1 +1 @@
Xat i VoIP segurs i descentralitzats. Protegeix les teves dades de tercers.
Xats i VoIP segurs i descentralitzats. Protegeix les teves dades de tercers.

View file

@ -1 +1 @@
Element (anteriorment Riot.im)
Element (abans Riot.im)

View file

@ -1,2 +1,2 @@
Hauptänderungen in dieser Version: URL-Vorschau, neue Emoji-Tastatur, neue Raumeinstellungen und Schnee für Weihnachten!
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Hauptänderungen in dieser Version: Bearbeiten von Raumberechtigungen, automatisches Hell/Dunkel-Design und eine Reihe von Fehlerkorrekturen.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Hauptänderungen in dieser Version: Unterstützung für soziale Anmeldungen.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Hauptänderungen in dieser Version: Unterstützung für soziale Anmeldungen.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,2 +1,2 @@
Olulisemad muutused selles versioonis: URLide eelvaade, uus klahvistik emojide jaoks, jututubade uued seadistused ja natuke lund jõuludeks!
Muudatuste logi täismahus: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Muudatuste logi täismahus: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Olulisemad muutused selles versioonis: Jututoa õiguste muutmine, automaatne tumeda ja heleda teema vahetamine ning märgatav kogus veaparandusi.
Muudatuste logi täismahus: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Olulisemad muutused selles versioonis: Sisselogimine sotsiaalmeediakontode abil.
Muudatuste logi täismahus: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Olulisemad muutused selles versioonis: Sisselogimine sotsiaalmeediakontode abil.
Muudatuste logi täismahus: https://github.com/vector-im/element-android/releases/tag/v1.0.15 ja https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,2 +1,2 @@
Principais mudanças nessa versão: Prévia do endereço URL, novo teclado de Emojis, novos recursos de configuração da sala, e neve para o Natal!
Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Principais mudanças nessa versão: editar permissões da sala, tema automaticamente claro/escuro e várias correções de erros.
Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Principais mudanças nessa versão: suporte para Login Social.
Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Principais mudanças nessa versão: suporte para Login Social.
Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.0.15 e https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,2 +1,2 @@
Основные изменения в этой версии: предварительный просмотр URL, новая клавиатура эмодзи, новые возможности настройки комнаты и снег на Рождество!
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Редактирование разрешений для комнаты, автоматическая светлая/темная тема и множество исправлений ошибок.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Поддержка входа в социальные сети.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Поддержка входа в социальные сети.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,2 +1,2 @@
Главне измене у овој верзији: УРЛ преглед, нова емоџи тастатура, нове могућности у поставкама собе и снег за Божић !
Дневник свих измена: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Главне измене у овој верзији: УРЛ преглед, нова емоџи тастатура, нове могућности у поставкама собе и снег за Божић!
Дневник свих измена: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Главна измена у овој верзији: уређивање дозвола у соби, аутоматска светла/тамна тема и гомила исправљених грешака.
Цео дневник измена: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Главна измена у овој верзији: подршка за пријављивање са друштвених мрежа.
Цео дневник измена: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Главна измена у овој верзији: подршка за пријављивање са друштвених мрежа.
Цео дневник измена: https://github.com/vector-im/element-android/releases/tag/v1.0.15 и https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,2 +1,2 @@
Huvudsakliga ändringar i den här versionen: URL-förhandsgranskning, nya rumsinställningsförmågor, och en vit jul!
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Huvudsakliga ändringar i den här versionen: Redigering av rumsbehörigheter, automatiskt ljust/mörkt tema, och en hög buggfixar.
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Huvudsakliga ändringar i den här versionen: Stöd för social inloggning.
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Huvudsakliga ändringar i den här versionen: Stöd för social inloggning.
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15 och https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,2 +1,2 @@
Основні зміни в цій версії: попередній перегляд URL-адреси, нова клавіатура Emoji, нові можливості налаштування кімнати та сніг на Різдво!
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Основні зміни в цій версії: попередній перегляд URL-адрес, нова клавіатура Emoji, нові можливості налаштування кімнати та сніг на Різдво!
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Основні зміни цієї версії: Керування дозволами кімнати, автоперемикання між світлою/темною темами та виправлення багатьох вад.
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Основні зміни цієї версії: підтримка входу за допомогою суспільних мереж.
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Основні зміни цієї версії: підтримка входу за допомогою суспільних мереж.
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.15 та https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,2 +1,2 @@
此版本中的主要變更URL 預覽、新的表情符號鍵盤、新的聊天室設定功能以及聖誕節降雪!
完整變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.0.12
完整變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
此版本的主要變動:編輯聊天室權限、自動淺色/深色佈景主題與許多臭蟲修復。
完整變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
此版本的主要變動:社群網路登入支援。
完整變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
此版本的主要變動:社群網路登入支援。
完整變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.0.15 以及 https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -1,3 +1,4 @@
#Fri Jan 29 18:05:42 CET 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=1433372d903ffba27496f8d5af24265310d2da0d78bf6b4e5138831d4fe066e9

View file

@ -168,12 +168,6 @@ dependencies {
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
// Web RTC
// org.webrtc:google-webrtc is for development purposes only. See http://webrtc.github.io/webrtc-org/native-code/android/
// implementation 'org.webrtc:google-webrtc:1.0.+'
// Use the same WebRTC library than the one used by Jitsi library
implementation('com.facebook.react:react-native-webrtc:1.84.0-jitsi-5112273@aar')
testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'

View file

@ -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
) {
/**

View file

@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.signout.SignOutService
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
import org.matrix.android.sdk.api.session.user.UserService
import org.matrix.android.sdk.api.session.widgets.WidgetService
@ -212,6 +213,11 @@ interface Session :
*/
fun searchService(): SearchService
/**
* Returns the third party service associated with the session
*/
fun thirdPartyService(): ThirdPartyService
/**
* Add a listener to the session.
* @param listener the listener to add.

View file

@ -20,8 +20,11 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
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.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
interface CallsListener {
interface CallListener {
/**
* Called when there is an incoming call within the room.
*/
@ -39,5 +42,23 @@ interface CallsListener {
*/
fun onCallHangupReceived(callHangupContent: CallHangupContent)
/**
* Called when a called has been rejected
*/
fun onCallRejectReceived(callRejectContent: CallRejectContent)
/**
* Called when an answer has been selected
*/
fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent)
/**
* Called when a negotiation is sent
*/
fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent)
/**
* Called when the call has been managed by an other session
*/
fun onCallManagedByOtherSession(callId: String)
}

View file

@ -28,9 +28,9 @@ interface CallSignalingService {
*/
fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall
fun addCallListener(listener: CallsListener)
fun addCallListener(listener: CallListener)
fun removeCallListener(listener: CallsListener)
fun removeCallListener(listener: CallListener)
fun getCallWithId(callId: String): MxCall?

View file

@ -16,13 +16,16 @@
package org.matrix.android.sdk.api.session.call
import org.webrtc.PeerConnection
sealed class CallState {
/** Idle, setting up objects */
object Idle : CallState()
/**
* CreateOffer. Intermediate state between Idle and Dialing.
*/
object CreateOffer: CallState()
/** Dialing. Outgoing call is signaling the remote peer */
object Dialing : CallState()
@ -36,8 +39,8 @@ sealed class CallState {
* Connected. Incoming/Outgoing call, ice layer connecting or connected
* Notice that the PeerState failed is not always final, if you switch network, new ice candidtates
* could be exchanged, and the connection could go back to connected
*/
data class Connected(val iceConnectionState: PeerConnection.PeerConnectionState) : CallState()
* */
data class Connected(val iceConnectionState: MxPeerConnectionState) : CallState()
/** Terminated. Incoming/Outgoing call, the call is terminated */
object Terminated : CallState()

View file

@ -16,14 +16,17 @@
package org.matrix.android.sdk.api.session.call
import org.webrtc.IceCandidate
import org.webrtc.SessionDescription
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
interface MxCallDetail {
val callId: String
val isOutgoing: Boolean
val roomId: String
val otherUserId: String
val opponentUserId: String
val isVideoCall: Boolean
}
@ -32,40 +35,64 @@ interface MxCallDetail {
*/
interface MxCall : MxCallDetail {
companion object {
const val VOIP_PROTO_VERSION = 1
}
val ourPartyId: String
var opponentPartyId: Optional<String>?
var opponentVersion: Int
var capabilities: CallCapabilities?
var state: CallState
/**
* Pick Up the incoming call
* It has no effect on outgoing call
*/
fun accept(sdp: SessionDescription)
fun accept(sdpString: String)
/**
* SDP negotiation for media pause, hold/resume, ICE restarts and voice/video call up/downgrading
*/
fun negotiate(sdpString: String, type: SdpType)
/**
* This has to be sent by the caller's client once it has chosen an answer.
*/
fun selectAnswer()
/**
* Reject an incoming call
* It's an alias to hangUp
*/
fun reject() = hangUp()
fun reject()
/**
* End the call
*/
fun hangUp()
fun hangUp(reason: CallHangupContent.Reason? = null)
/**
* Start a call
* Send offer SDP to the other participant.
*/
fun offerSdp(sdp: SessionDescription)
fun offerSdp(sdpString: String)
/**
* Send Ice candidate to the other participant.
* Send Call candidate to the other participant.
*/
fun sendLocalIceCandidates(candidates: List<IceCandidate>)
fun sendLocalCallCandidates(candidates: List<CallCandidate>)
/**
* Send removed ICE candidates to the other participant.
*/
fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>)
fun sendLocalIceCandidateRemovals(candidates: List<CallCandidate>)
/**
* 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)

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 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.call;
/**
* This is a copy of https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState
* to avoid having the dependency over WebRtc library on sdk.
*/
public enum MxPeerConnectionState {
NEW,
CONNECTING,
CONNECTED,
DISCONNECTED,
FAILED,
CLOSED
}

View file

@ -68,7 +68,12 @@ object EventType {
const val CALL_INVITE = "m.call.invite"
const val CALL_CANDIDATES = "m.call.candidates"
const val CALL_ANSWER = "m.call.answer"
const val CALL_SELECT_ANSWER = "m.call.select_answer"
const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup"
// This type is not processed by the client, just sent to the server
const val CALL_REPLACES = "m.call.replaces"
// Key share events
const val ROOM_KEY_REQUEST = "m.room_key_request"
@ -98,5 +103,9 @@ object EventType {
|| type == CALL_CANDIDATES
|| type == CALL_ANSWER
|| type == CALL_HANGUP
|| type == CALL_SELECT_ANSWER
|| type == CALL_NEGOTIATE
|| type == CALL_REJECT
|| type == CALL_REPLACES
}
}

View file

@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
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.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.util.Cancelable
/**
@ -35,12 +34,6 @@ interface RoomDirectoryService {
publicRoomsParams: PublicRoomsParams,
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
/**
* Fetches the overall metadata about protocols supported by the homeserver.
* Includes both the available protocols and all fields required for queries against each protocol.
*/
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable
/**
* Get the visibility of a room in the directory
*/

View file

@ -27,16 +27,24 @@ data class CallAnswerContent(
/**
* Required. The ID of the call this event relates to.
*/
@Json(name = "call_id") val callId: String,
@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,
/**
* Required. The session description object
*/
@Json(name = "answer") val answer: Answer,
/**
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
* Required. The version of the VoIP specification this messages adheres to.
*/
@Json(name = "version") val version: Int = 0
) {
@Json(name = "version") override val version: String?,
/**
* Capability advertisement.
*/
@Json(name = "capabilities") val capabilities: CallCapabilities? = null
): CallSignallingContent {
@JsonClass(generateAdapter = true)
data class Answer(

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 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
@JsonClass(generateAdapter = true)
data class CallCandidate(
/**
* Required. The SDP media type this candidate is intended for.
*/
@Json(name = "sdpMid") val sdpMid: String? = null,
/**
* Required. The index of the SDP 'm' line this candidate is intended for.
*/
@Json(name = "sdpMLineIndex") val sdpMLineIndex: Int = 0,
/**
* Required. The SDP 'a' line of the candidate.
*/
@Json(name = "candidate") val candidate: String? = null
)

View file

@ -28,30 +28,17 @@ data class CallCandidatesContent(
/**
* Required. The ID of the call this event relates to.
*/
@Json(name = "call_id") val callId: String,
@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,
/**
* Required. Array of objects describing the candidates.
*/
@Json(name = "candidates") val candidates: List<Candidate> = emptyList(),
@Json(name = "candidates") val candidates: List<CallCandidate> = emptyList(),
/**
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
* Required. The version of the VoIP specification this messages adheres to.
*/
@Json(name = "version") val version: Int = 0
) {
@JsonClass(generateAdapter = true)
data class Candidate(
/**
* Required. The SDP media type this candidate is intended for.
*/
@Json(name = "sdpMid") val sdpMid: String,
/**
* Required. The index of the SDP 'm' line this candidate is intended for.
*/
@Json(name = "sdpMLineIndex") val sdpMLineIndex: Int,
/**
* Required. The SDP 'a' line of the candidate.
*/
@Json(name = "candidate") val candidate: String
)
}
@Json(name = "version") override val version: String?
): CallSignallingContent

View file

@ -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()

View file

@ -28,24 +28,41 @@ data class CallHangupContent(
/**
* Required. The ID of the call this event relates to.
*/
@Json(name = "call_id") val callId: String,
@Json(name = "call_id") override val callId: String,
/**
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
* Required. ID to let user identify remote echo of their own events
*/
@Json(name = "version") val version: Int = 0,
@Json(name = "party_id") override val partyId: String? = null,
/**
* Required. The version of the VoIP specification this message adheres to.
*/
@Json(name = "version") override val version: String?,
/**
* Optional error reason for the hangup. This should not be provided when the user naturally ends or rejects the call.
* When there was an error in the call negotiation, this should be `ice_failed` for when ICE negotiation fails
* or `invite_timeout` for when the other party did not answer in time. One of: ["ice_failed", "invite_timeout"]
* or `invite_timeout` for when the other party did not answer in time.
* One of: ["ice_failed", "invite_timeout"]
*/
@Json(name = "reason") val reason: Reason? = null
) {
) : CallSignallingContent {
@JsonClass(generateAdapter = false)
enum class Reason {
@Json(name = "ice_failed")
ICE_FAILED,
@Json(name = "ice_timeout")
ICE_TIMEOUT,
@Json(name = "user_hangup")
USER_HANGUP,
@Json(name = "user_media_failed")
USER_MEDIA_FAILED,
@Json(name = "invite_timeout")
INVITE_TIMEOUT
INVITE_TIMEOUT,
@Json(name = "unknown_error")
UNKWOWN_ERROR
}
}

View file

@ -27,22 +27,35 @@ data class CallInviteContent(
/**
* Required. A unique identifier for the call.
*/
@Json(name = "call_id") val callId: String?,
@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,
/**
* Required. The session description object
*/
@Json(name = "offer") val offer: Offer?,
/**
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
* Required. The version of the VoIP specification this message adheres to.
*/
@Json(name = "version") val version: Int? = 0,
@Json(name = "version") override val version: String?,
/**
* Required. The time in milliseconds that the invite is valid for.
* Once the invite age exceeds this value, clients should discard it.
* They should also no longer show the call as awaiting an answer in the UI.
*/
@Json(name = "lifetime") val lifetime: Int?
) {
@Json(name = "lifetime") val lifetime: Int?,
/**
* The field should be added for all invites where the target is a specific user
*/
@Json(name = "invitee") val invitee: String? = null,
/**
* Capability advertisement.
*/
@Json(name = "capabilities") val capabilities: CallCapabilities? = null
): CallSignallingContent {
@JsonClass(generateAdapter = true)
data class Offer(
/**

View file

@ -0,0 +1,62 @@
/*
* 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 introduces SDP negotiation semantics for media pause, hold/resume, ICE restarts and voice/video call up/downgrading.
*/
@JsonClass(generateAdapter = true)
data class CallNegotiateContent(
/**
* 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,
/**
* Required. The time in milliseconds that the negotiation is valid for. Once exceeded the sender
* of the negotiate event should consider the negotiation failed (timed out) and the recipient should ignore it.
**/
@Json(name = "lifetime") val lifetime: Int?,
/**
* Required. The session description object
*/
@Json(name = "description") val description: Description? = null,
/**
* Required. The version of the VoIP specification this message adheres to.
*/
@Json(name = "version") override val version: String?
): CallSignallingContent {
@JsonClass(generateAdapter = true)
data class Description(
/**
* Required. The type of session description.
*/
@Json(name = "type") val type: SdpType?,
/**
* Required. The SDP text of the session description.
*/
@Json(name = "sdp") val sdp: String?
)
}

View file

@ -0,0 +1,40 @@
/*
* 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
/**
* Sent by either party to signal their termination of the call. This can be sent either once
* the call has been established or before to abort the call.
*/
@JsonClass(generateAdapter = true)
data class CallRejectContent(
/**
* 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,
/**
* Required. The version of the VoIP specification this message adheres to.
*/
@Json(name = "version") override val version: String?
) : CallSignallingContent

View file

@ -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?
)
}

View file

@ -0,0 +1,44 @@
/*
* 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 by the callee when they wish to answer the call.
*/
@JsonClass(generateAdapter = true)
data class CallSelectAnswerContent(
/**
* 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,
/**
* Required. Indicates the answer user has chosen.
*/
@Json(name = "selected_party_id") val selectedPartyId: String? = null,
/**
* Required. The version of the VoIP specification this message adheres to.
*/
@Json(name = "version") override val version: String?
): CallSignallingContent

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 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
interface CallSignallingContent {
/**
* Required. A unique identifier for the call.
*/
val callId: String?
/**
* Required. ID to let user identify remote echo of their own events
*/
val partyId: String?
/**
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
*/
val version: String?
}

View file

@ -25,5 +25,5 @@ enum class SdpType {
OFFER,
@Json(name = "answer")
ANSWER
ANSWER;
}

View file

@ -20,11 +20,15 @@ import org.matrix.android.sdk.api.session.events.model.EventType
object RoomSummaryConstants {
/**
*
*/
val PREVIEWABLE_TYPES = listOf(
// TODO filter message type (KEY_VERIFICATION_READY, etc.)
EventType.MESSAGE,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_REJECT,
EventType.CALL_ANSWER,
EventType.ENCRYPTED,
EventType.STICKER,

View file

@ -52,6 +52,8 @@ data class TimelineEvent(
}
}
val roomId = root.roomId ?: ""
val metadata = HashMap<String, Any>()
/**

View file

@ -0,0 +1,39 @@
/*
* 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.api.session.thirdparty
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser
/**
* See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-thirdparty-protocols
*/
interface ThirdPartyService {
/**
* Fetches the overall metadata about protocols supported by the homeserver.
* Includes both the available protocols and all fields required for queries against each protocol.
*/
suspend fun getThirdPartyProtocols(): Map<String, ThirdPartyProtocol>
/**
* Retrieve a Matrix User ID linked to a user on the third party service, given a set of user parameters.
* @param protocol Required. The name of the protocol.
* @param fields One or more custom fields that are passed to the AS to help identify the user.
*/
suspend fun getThirdPartyUser(protocol: String, fields: Map<String, String> = emptyMap()): List<ThirdPartyUser>
}

View file

@ -0,0 +1,37 @@
/*
* 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.api.session.thirdparty.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.util.JsonDict
@JsonClass(generateAdapter = true)
data class ThirdPartyUser(
/*
Required. A Matrix User ID represting a third party user.
*/
@Json(name = "userid") val userId: String,
/*
Required. The protocol ID that the third party location is a part of.
*/
@Json(name = "protocol") val protocol: String,
/*
Required. Information used to identify this third party location.
*/
@Json(name = "fields") val fields: JsonDict
)

View file

@ -56,6 +56,11 @@ interface WidgetService {
excludedTypes: Set<String>? = null
): List<Widget>
/**
* Return the computed URL of a widget
*/
fun getWidgetComputedUrl(widget: Widget, isLightTheme: Boolean): String?
/**
* Returns the live room widgets so you can listen to them.
* Some widgets can be deactivated, so be sure to check for isActive.

View file

@ -25,7 +25,6 @@ data class Widget(
val widgetId: String,
val senderInfo: SenderInfo?,
val isAddedByMe: Boolean,
val computedUrl: String?,
val type: WidgetType
) {

View file

@ -71,7 +71,6 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
return@forEach
}
val domainEvent = event.asDomain()
// decryptIfNeeded(domainEvent)
processors.filter {
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
}.forEach {
@ -83,6 +82,7 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
.findAll()
.deleteAllFromRealm()
}
processors.forEach { it.onPostProcess() }
}
}

View file

@ -49,6 +49,7 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi
import org.matrix.android.sdk.api.session.signout.SignOutService
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
import org.matrix.android.sdk.api.session.user.UserService
import org.matrix.android.sdk.api.session.widgets.WidgetService
@ -114,6 +115,7 @@ internal class DefaultSession @Inject constructor(
private val accountService: Lazy<AccountService>,
private val defaultIdentityService: DefaultIdentityService,
private val integrationManagerService: IntegrationManagerService,
private val thirdPartyService: Lazy<ThirdPartyService>,
private val callSignalingService: Lazy<CallSignalingService>,
@UnauthenticatedWithCertificate
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>,
@ -258,6 +260,8 @@ internal class DefaultSession @Inject constructor(
override fun searchService(): SearchService = searchService.get()
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
override fun getOkHttpClient(): OkHttpClient {
return unauthenticatedWithCertificateOkHttpClient.get()
}

View file

@ -25,4 +25,12 @@ internal interface EventInsertLiveProcessor {
fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean
suspend fun process(realm: Realm, event: Event)
/**
* Called after transaction.
* Maybe you prefer to process the events outside of the realm transaction.
*/
suspend fun onPostProcess() {
// Noop by default
}
}

View file

@ -56,6 +56,7 @@ import org.matrix.android.sdk.internal.session.sync.SyncTask
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
import org.matrix.android.sdk.internal.session.terms.TermsModule
import org.matrix.android.sdk.internal.session.thirdparty.ThirdPartyModule
import org.matrix.android.sdk.internal.session.user.UserModule
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataModule
import org.matrix.android.sdk.internal.session.widgets.WidgetModule
@ -87,7 +88,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
ProfileModule::class,
AccountModule::class,
CallModule::class,
SearchModule::class
SearchModule::class,
ThirdPartyModule::class
]
)
@SessionScope

View file

@ -16,28 +16,30 @@
package org.matrix.android.sdk.internal.session.call
import io.realm.Realm
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.internal.database.model.EventInsertType
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
import io.realm.Realm
import timber.log.Timber
import javax.inject.Inject
internal class CallEventProcessor @Inject constructor(
@UserId private val userId: String,
private val callService: DefaultCallSignalingService
) : EventInsertLiveProcessor {
internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler)
: EventInsertLiveProcessor {
private val allowedTypes = listOf(
EventType.CALL_ANSWER,
EventType.CALL_SELECT_ANSWER,
EventType.CALL_REJECT,
EventType.CALL_NEGOTIATE,
EventType.CALL_CANDIDATES,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.ENCRYPTED
)
private val eventsToPostProcess = mutableListOf<Event>()
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
if (insertType != EventInsertType.INCREMENTAL_SYNC) {
return false
@ -46,10 +48,17 @@ internal class CallEventProcessor @Inject constructor(
}
override suspend fun process(realm: Realm, event: Event) {
update(realm, event)
eventsToPostProcess.add(event)
}
private fun update(realm: Realm, event: Event) {
override suspend fun onPostProcess() {
eventsToPostProcess.forEach {
dispatchToCallSignalingHandlerIfNeeded(it)
}
eventsToPostProcess.clear()
}
private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
val now = System.currentTimeMillis()
// TODO might check if an invite is not closed (hangup/answsered) in the same event batch?
event.roomId ?: return Unit.also {
@ -60,10 +69,6 @@ internal class CallEventProcessor @Inject constructor(
// To old to ring?
return
}
event.ageLocalTs
if (EventType.isCallEvent(event.getClearType())) {
callService.onCallEvent(event)
}
Timber.v("$realm : $userId")
callSignalingHandler.onCallEvent(event)
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 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.internal.session.call
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
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.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
/**
* Dispatch each method safely to all listeners.
*/
internal class CallListenersDispatcher(private val listeners: Set<CallListener>) : CallListener {
override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) = dispatch {
it.onCallInviteReceived(mxCall, callInviteContent)
}
override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) = dispatch {
it.onCallIceCandidateReceived(mxCall, iceCandidatesContent)
}
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) = dispatch {
it.onCallAnswerReceived(callAnswerContent)
}
override fun onCallHangupReceived(callHangupContent: CallHangupContent) = dispatch {
it.onCallHangupReceived(callHangupContent)
}
override fun onCallRejectReceived(callRejectContent: CallRejectContent) = dispatch {
it.onCallRejectReceived(callRejectContent)
}
override fun onCallManagedByOtherSession(callId: String) = dispatch {
it.onCallManagedByOtherSession(callId)
}
override fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) = dispatch {
it.onCallSelectAnswerReceived(callSelectAnswerContent)
}
override fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent) = dispatch {
it.onCallNegotiateReceived(callNegotiateContent)
}
private fun dispatch(lambda: (CallListener) -> Unit) {
listeners.toList().forEach {
tryOrNull {
lambda(it)
}
}
}
}

View file

@ -0,0 +1,218 @@
/*
* Copyright (c) 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.internal.session.call
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.events.model.Event
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
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallSignallingContent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject
@SessionScope
internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
private val mxCallFactory: MxCallFactory,
@UserId private val userId: String) {
private val callListeners = mutableSetOf<CallListener>()
private val callListenersDispatcher = CallListenersDispatcher(callListeners)
fun addCallListener(listener: CallListener) {
callListeners.add(listener)
}
fun removeCallListener(listener: CallListener) {
callListeners.remove(listener)
}
fun onCallEvent(event: Event) {
when (event.getClearType()) {
EventType.CALL_ANSWER -> {
handleCallAnswerEvent(event)
}
EventType.CALL_INVITE -> {
handleCallInviteEvent(event)
}
EventType.CALL_HANGUP -> {
handleCallHangupEvent(event)
}
EventType.CALL_REJECT -> {
handleCallRejectEvent(event)
}
EventType.CALL_CANDIDATES -> {
handleCallCandidatesEvent(event)
}
EventType.CALL_SELECT_ANSWER -> {
handleCallSelectAnswerEvent(event)
}
EventType.CALL_NEGOTIATE -> {
handleCallNegotiateEvent(event)
}
}
}
private fun handleCallNegotiateEvent(event: Event) {
val content = event.getClearContent().toModel<CallNegotiateContent>() ?: return
val call = content.getCall() ?: return
if (call.ourPartyId == content.partyId) {
// Ignore remote echo
return
}
callListenersDispatcher.onCallNegotiateReceived(content)
}
private fun handleCallSelectAnswerEvent(event: Event) {
val content = event.getClearContent().toModel<CallSelectAnswerContent>() ?: return
val call = content.getCall() ?: return
if (call.ourPartyId == content.partyId) {
// Ignore remote echo
return
}
if (call.isOutgoing) {
Timber.v("Got selectAnswer for an outbound call: ignoring")
return
}
val selectedPartyId = content.selectedPartyId
if (selectedPartyId == null) {
Timber.w("Got nonsensical select_answer with null selected_party_id: ignoring")
return
}
callListenersDispatcher.onCallSelectAnswerReceived(content)
}
private fun handleCallCandidatesEvent(event: Event) {
val content = event.getClearContent().toModel<CallCandidatesContent>() ?: return
val call = content.getCall() ?: return
if (call.ourPartyId == content.partyId) {
// Ignore remote echo
return
}
if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
Timber.v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
return
}
callListenersDispatcher.onCallIceCandidateReceived(call, content)
}
private fun handleCallRejectEvent(event: Event) {
val content = event.getClearContent().toModel<CallRejectContent>() ?: return
val call = content.getCall() ?: return
if (call.ourPartyId == content.partyId) {
// Ignore remote echo
return
}
activeCallHandler.removeCall(content.callId)
if (event.senderId == userId) {
// discard current call, it's rejected by another of my session
callListenersDispatcher.onCallManagedByOtherSession(content.callId)
return
}
// No need to check party_id for reject because if we'd received either
// an answer or reject, we wouldn't be in state InviteSent
if (call.state != CallState.Dialing) {
return
}
callListenersDispatcher.onCallRejectReceived(content)
}
private fun handleCallHangupEvent(event: Event) {
val content = event.getClearContent().toModel<CallHangupContent>() ?: return
val call = content.getCall() ?: return
// party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen
// a partner yet but we're treating the hangup as a reject as per VoIP v0)
if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
Timber.v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
return
}
if (call.state != CallState.Terminated) {
activeCallHandler.removeCall(content.callId)
callListenersDispatcher.onCallHangupReceived(content)
}
}
private fun handleCallInviteEvent(event: Event) {
if (event.senderId == userId) {
// ignore invites you send
return
}
if (event.roomId == null || event.senderId == null) {
return
}
val content = event.getClearContent().toModel<CallInviteContent>() ?: return
val incomingCall = mxCallFactory.createIncomingCall(
roomId = event.roomId,
opponentUserId = event.senderId,
content = content
) ?: return
activeCallHandler.addCall(incomingCall)
callListenersDispatcher.onCallInviteReceived(incomingCall, content)
}
private fun handleCallAnswerEvent(event: Event) {
val content = event.getClearContent().toModel<CallAnswerContent>() ?: return
val call = content.getCall() ?: return
if (call.ourPartyId == content.partyId) {
// Ignore remote echo
return
}
if (event.senderId == userId) {
// discard current call, it's answered by another of my session
activeCallHandler.removeCall(call.callId)
callListenersDispatcher.onCallManagedByOtherSession(content.callId)
} else {
if (call.opponentPartyId != null) {
Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
return
}
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)
}
}
private fun MxCall.partyIdsMatches(contentSignallingContent: CallSignallingContent): Boolean {
return opponentPartyId?.getOrNull() == contentSignallingContent.partyId
}
private fun CallSignallingContent.getCall(): MxCall? {
val currentCall = callId?.let {
activeCallHandler.getCallWithId(it)
}
if (currentCall == null) {
Timber.v("Call with id $callId is null")
}
return currentCall
}
}

View file

@ -16,106 +16,46 @@
package org.matrix.android.sdk.internal.session.call
import android.os.SystemClock
import kotlinx.coroutines.Dispatchers
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.CallsListener
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.TurnServerResponse
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
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@SessionScope
internal class DefaultCallSignalingService @Inject constructor(
@UserId
private val userId: String,
private val callSignalingHandler: CallSignalingHandler,
private val mxCallFactory: MxCallFactory,
private val activeCallHandler: ActiveCallHandler,
private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor,
private val taskExecutor: TaskExecutor,
private val turnServerTask: GetTurnServerTask
private val turnServerDataSource: TurnServerDataSource
) : CallSignalingService {
private val callListeners = mutableSetOf<CallsListener>()
private val cachedTurnServerResponse = object {
// Keep one minute safe to avoid considering the data is valid and then actually it is not when effectively using it.
private val MIN_TTL = 60
private val now = { SystemClock.elapsedRealtime() / 1000 }
private var expiresAt: Long = 0
var data: TurnServerResponse? = null
get() = if (expiresAt > now()) field else null
set(value) {
expiresAt = now() + (value?.ttl ?: 0) - MIN_TTL
field = value
}
}
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
if (cachedTurnServerResponse.data != null) {
cachedTurnServerResponse.data?.let { callback.onSuccess(it) }
return NoOpCancellable
return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) {
turnServerDataSource.getTurnServer()
}
return turnServerTask
.configureWith(GetTurnServerTask.Params) {
this.callback = object : MatrixCallback<TurnServerResponse> {
override fun onSuccess(data: TurnServerResponse) {
cachedTurnServerResponse.data = data
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
}
.executeBy(taskExecutor)
}
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
val call = MxCallImpl(
callId = UUID.randomUUID().toString(),
isOutgoing = true,
roomId = roomId,
userId = userId,
otherUserId = otherUserId,
isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor
)
activeCallHandler.addCall(call).also {
return call
return mxCallFactory.createOutgoingCall(roomId, otherUserId, isVideoCall).also {
activeCallHandler.addCall(it)
}
}
override fun addCallListener(listener: CallsListener) {
callListeners.add(listener)
override fun addCallListener(listener: CallListener) {
callSignalingHandler.addCallListener(listener)
}
override fun removeCallListener(listener: CallsListener) {
callListeners.remove(listener)
override fun removeCallListener(listener: CallListener) {
callSignalingHandler.removeCallListener(listener)
}
override fun getCallWithId(callId: String): MxCall? {
@ -127,129 +67,6 @@ internal class DefaultCallSignalingService @Inject constructor(
return activeCallHandler.getActiveCallsLiveData().value?.isNotEmpty() == true
}
internal fun onCallEvent(event: Event) {
when (event.getClearType()) {
EventType.CALL_ANSWER -> {
event.getClearContent().toModel<CallAnswerContent>()?.let {
if (event.senderId == userId) {
// ok it's an answer from me.. is it remote echo or other session
val knownCall = getCallWithId(it.callId)
if (knownCall == null) {
Timber.d("## VOIP onCallEvent ${event.getClearType()} id ${it.callId} send by me")
} else if (!knownCall.isOutgoing) {
// incoming call
// if it was anwsered by this session, the call state would be in Answering(or connected) state
if (knownCall.state == CallState.LocalRinging) {
// discard current call, it's answered by another of my session
onCallManageByOtherSession(it.callId)
}
}
return
}
onCallAnswer(it)
}
}
EventType.CALL_INVITE -> {
if (event.senderId == userId) {
// Always ignore local echos of invite
return
}
event.getClearContent().toModel<CallInviteContent>()?.let { content ->
val incomingCall = MxCallImpl(
callId = content.callId ?: return@let,
isOutgoing = false,
roomId = event.roomId ?: return@let,
userId = userId,
otherUserId = event.senderId ?: return@let,
isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor
)
activeCallHandler.addCall(incomingCall)
onCallInvite(incomingCall, content)
}
}
EventType.CALL_HANGUP -> {
event.getClearContent().toModel<CallHangupContent>()?.let { content ->
if (event.senderId == userId) {
// ok it's an answer from me.. is it remote echo or other session
val knownCall = getCallWithId(content.callId)
if (knownCall == null) {
Timber.d("## VOIP onCallEvent ${event.getClearType()} id ${content.callId} send by me")
} else if (!knownCall.isOutgoing) {
// incoming call
if (knownCall.state == CallState.LocalRinging) {
// discard current call, it's answered by another of my session
onCallManageByOtherSession(content.callId)
}
}
return
}
activeCallHandler.removeCall(content.callId)
onCallHangup(content)
}
}
EventType.CALL_CANDIDATES -> {
if (event.senderId == userId) {
// Always ignore local echos of invite
return
}
event.getClearContent().toModel<CallCandidatesContent>()?.let { content ->
activeCallHandler.getCallWithId(content.callId)?.let {
onCallIceCandidate(it, content)
}
}
}
}
}
private fun onCallHangup(hangup: CallHangupContent) {
callListeners.toList().forEach {
tryOrNull {
it.onCallHangupReceived(hangup)
}
}
}
private fun onCallAnswer(answer: CallAnswerContent) {
callListeners.toList().forEach {
tryOrNull {
it.onCallAnswerReceived(answer)
}
}
}
private fun onCallManageByOtherSession(callId: String) {
callListeners.toList().forEach {
tryOrNull {
it.onCallManagedByOtherSession(callId)
}
}
}
private fun onCallInvite(incomingCall: MxCall, invite: CallInviteContent) {
// Ignore the invitation from current user
if (incomingCall.otherUserId == userId) return
callListeners.toList().forEach {
tryOrNull {
it.onCallInviteReceived(incomingCall, invite)
}
}
}
private fun onCallIceCandidate(incomingCall: MxCall, candidates: CallCandidatesContent) {
callListeners.toList().forEach {
tryOrNull {
it.onCallIceCandidateReceived(incomingCall, candidates)
}
}
}
companion object {
const val CALL_TIMEOUT_MS = 120_000
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 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.internal.session.call
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import java.math.BigDecimal
import java.util.UUID
import javax.inject.Inject
internal class MxCallFactory @Inject constructor(
@DeviceId private val deviceId: String?,
private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor,
private val matrixConfiguration: MatrixConfiguration,
private val getProfileInfoTask: GetProfileInfoTask,
@UserId private val userId: String
) {
fun createIncomingCall(roomId: String, opponentUserId: String, content: CallInviteContent): MxCall? {
content.callId ?: return null
return MxCallImpl(
callId = content.callId,
isOutgoing = false,
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = opponentUserId,
isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration,
getProfileInfoTask = getProfileInfoTask
).apply {
opponentPartyId = Optional.from(content.partyId)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
capabilities = content.capabilities ?: CallCapabilities()
}
}
fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall {
return MxCallImpl(
callId = UUID.randomUUID().toString(),
isOutgoing = true,
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = opponentUserId,
isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration,
getProfileInfoTask = getProfileInfoTask
)
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 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.internal.session.call
import android.os.SystemClock
import org.matrix.android.sdk.api.session.call.TurnServerResponse
import javax.inject.Inject
internal class TurnServerDataSource @Inject constructor(private val turnServerTask: GetTurnServerTask) {
private val cachedTurnServerResponse = object {
// Keep one minute safe to avoid considering the data is valid and then actually it is not when effectively using it.
private val MIN_TTL = 60
private val now = { SystemClock.elapsedRealtime() / 1000 }
private var expiresAt: Long = 0
var data: TurnServerResponse? = null
get() = if (expiresAt > now()) field else null
set(value) {
expiresAt = now() + (value?.ttl ?: 0) - MIN_TTL
field = value
}
}
suspend fun getTurnServer(): TurnServerResponse {
return cachedTurnServerResponse.data ?: turnServerTask.execute(GetTurnServerTask.Params).also {
cachedTurnServerResponse.data = it
}
}
}

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.call.model
import org.matrix.android.sdk.api.MatrixConfiguration
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.events.model.Content
@ -24,28 +25,44 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.UnsignedData
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.profile.ProfileService
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidate
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
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.webrtc.IceCandidate
import org.webrtc.SessionDescription
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import timber.log.Timber
import java.util.UUID
internal class MxCallImpl(
override val callId: String,
override val isOutgoing: Boolean,
override val roomId: String,
private val userId: String,
override val otherUserId: String,
override val opponentUserId: String,
override val isVideoCall: Boolean,
override val ourPartyId: String,
private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor
private val eventSenderProcessor: EventSenderProcessor,
private val matrixConfiguration: MatrixConfiguration,
private val getProfileInfoTask: GetProfileInfoTask
) : MxCall {
override var opponentPartyId: Optional<String>? = null
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
override var capabilities: CallCapabilities? = null
override var state: CallState = CallState.Idle
set(value) {
field = value
@ -81,60 +98,135 @@ internal class MxCallImpl(
}
}
override fun offerSdp(sdp: SessionDescription) {
override fun offerSdp(sdpString: String) {
if (!isOutgoing) return
Timber.v("## VOIP offerSdp $callId")
state = CallState.Dialing
CallInviteContent(
callId = callId,
partyId = ourPartyId,
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
offer = CallInviteContent.Offer(sdp = sdp.description)
offer = CallInviteContent.Offer(sdp = sdpString),
version = MxCall.VOIP_PROTO_VERSION.toString(),
capabilities = buildCapabilities()
)
.let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
}
override fun sendLocalIceCandidates(candidates: List<IceCandidate>) {
override fun sendLocalCallCandidates(candidates: List<CallCandidate>) {
Timber.v("Send local call canditates $callId: $candidates")
CallCandidatesContent(
callId = callId,
candidates = candidates.map {
CallCandidatesContent.Candidate(
sdpMid = it.sdpMid,
sdpMLineIndex = it.sdpMLineIndex,
candidate = it.sdp
)
}
partyId = ourPartyId,
candidates = candidates,
version = MxCall.VOIP_PROTO_VERSION.toString()
)
.let { createEventAndLocalEcho(type = EventType.CALL_CANDIDATES, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
}
override fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>) {
override fun sendLocalIceCandidateRemovals(candidates: List<CallCandidate>) {
// For now we don't support this flow
}
override fun hangUp() {
override fun reject() {
if (opponentVersion < 1) {
Timber.v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject")
hangUp()
return
}
Timber.v("## VOIP reject $callId")
CallRejectContent(
callId = callId,
partyId = ourPartyId,
version = MxCall.VOIP_PROTO_VERSION.toString()
)
.let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
state = CallState.Terminated
}
override fun hangUp(reason: CallHangupContent.Reason?) {
Timber.v("## VOIP hangup $callId")
CallHangupContent(
callId = callId
callId = callId,
partyId = ourPartyId,
reason = reason ?: CallHangupContent.Reason.USER_HANGUP,
version = MxCall.VOIP_PROTO_VERSION.toString()
)
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
state = CallState.Terminated
}
override fun accept(sdp: SessionDescription) {
override fun accept(sdpString: String) {
Timber.v("## VOIP accept $callId")
if (isOutgoing) return
state = CallState.Answering
CallAnswerContent(
callId = callId,
answer = CallAnswerContent.Answer(sdp = sdp.description)
partyId = ourPartyId,
answer = CallAnswerContent.Answer(sdp = sdpString),
version = MxCall.VOIP_PROTO_VERSION.toString(),
capabilities = buildCapabilities()
)
.let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
}
override fun negotiate(sdpString: String, type: SdpType) {
Timber.v("## VOIP negotiate $callId")
CallNegotiateContent(
callId = callId,
partyId = ourPartyId,
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
description = CallNegotiateContent.Description(sdp = sdpString, type = type),
version = MxCall.VOIP_PROTO_VERSION.toString()
)
.let { createEventAndLocalEcho(type = EventType.CALL_NEGOTIATE, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
}
override fun selectAnswer() {
Timber.v("## VOIP select answer $callId")
if (isOutgoing) return
state = CallState.Answering
CallSelectAnswerContent(
callId = callId,
partyId = ourPartyId,
selectedPartyId = opponentPartyId?.getOrNull(),
version = MxCall.VOIP_PROTO_VERSION.toString()
)
.let { createEventAndLocalEcho(type = EventType.CALL_SELECT_ANSWER, roomId = roomId, content = it.toContent()) }
.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,
@ -147,4 +239,12 @@ internal class MxCallImpl(
)
.also { localEchoEventFactory.createLocalEcho(it) }
}
private fun buildCapabilities(): CallCapabilities? {
return if (matrixConfiguration.supportsCallTransfer) {
CallCapabilities(true)
} else {
null
}
}
}

View file

@ -37,7 +37,6 @@ import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataS
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber
import javax.inject.Inject
@ -55,7 +54,6 @@ import javax.inject.Inject
*/
@SessionScope
internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration,
private val taskExecutor: TaskExecutor,
@SessionDatabase private val monarchy: Monarchy,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val accountDataDataSource: AccountDataDataSource,

View file

@ -21,11 +21,9 @@ import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
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.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
@ -33,7 +31,6 @@ import javax.inject.Inject
internal class DefaultRoomDirectoryService @Inject constructor(
private val getPublicRoomTask: GetPublicRoomTask,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask,
private val taskExecutor: TaskExecutor) : RoomDirectoryService {
@ -48,14 +45,6 @@ internal class DefaultRoomDirectoryService @Inject constructor(
.executeBy(taskExecutor)
}
override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable {
return getThirdPartyProtocolsTask
.configureWith {
this.callback = callback
}
.executeBy(taskExecutor)
}
override suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility {
return getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))
}

View file

@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
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.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse
@ -50,14 +49,6 @@ import retrofit2.http.Query
internal interface RoomAPI {
/**
* Get the third party server protocols.
*
* Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-thirdparty-protocols
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols")
fun thirdPartyProtocols(): Call<Map<String, ThirdPartyProtocol>>
/**
* Lists the public rooms on the server, with optional filter.
* This API returns paginated responses. The rooms are ordered by the number of joined members, with the largest rooms first.

View file

@ -39,11 +39,9 @@ import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.membership.DefaultLoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
@ -153,9 +151,6 @@ internal abstract class RoomModule {
@Binds
abstract fun bindSetRoomDirectoryVisibilityTask(task: DefaultSetRoomDirectoryVisibilityTask): SetRoomDirectoryVisibilityTask
@Binds
abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
@Binds
abstract fun bindInviteTask(task: DefaultInviteTask): InviteTask

View file

@ -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)

View file

@ -0,0 +1,39 @@
/*
* 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.thirdparty
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser
import javax.inject.Inject
internal class DefaultThirdPartyService @Inject constructor(private val getThirdPartyProtocolTask: GetThirdPartyProtocolsTask,
private val getThirdPartyUserTask: GetThirdPartyUserTask)
: ThirdPartyService {
override suspend fun getThirdPartyProtocols(): Map<String, ThirdPartyProtocol> {
return getThirdPartyProtocolTask.execute(Unit)
}
override suspend fun getThirdPartyUser(protocol: String, fields: Map<String, String>): List<ThirdPartyUser> {
val taskParams = GetThirdPartyUserTask.Params(
protocol = protocol,
fields = fields
)
return getThirdPartyUserTask.execute(taskParams)
}
}

View file

@ -14,25 +14,24 @@
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.room.directory
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.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetThirdPartyProtocolsTask : Task<Unit, Map<String, ThirdPartyProtocol>>
internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(
private val roomAPI: RoomAPI,
private val thirdPartyAPI: ThirdPartyAPI,
private val globalErrorReceiver: GlobalErrorReceiver
) : GetThirdPartyProtocolsTask {
override suspend fun execute(params: Unit): Map<String, ThirdPartyProtocol> {
return executeRequest(globalErrorReceiver) {
apiCall = roomAPI.thirdPartyProtocols()
apiCall = thirdPartyAPI.thirdPartyProtocols()
}
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.internal.session.thirdparty
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
internal interface GetThirdPartyUserTask : Task<GetThirdPartyUserTask.Params, List<ThirdPartyUser>> {
data class Params(
val protocol: String,
val fields: Map<String, String> = emptyMap()
)
}
internal class DefaultGetThirdPartyUserTask @Inject constructor(
private val thirdPartyAPI: ThirdPartyAPI,
private val globalErrorReceiver: GlobalErrorReceiver
) : GetThirdPartyUserTask {
override suspend fun execute(params: GetThirdPartyUserTask.Params): List<ThirdPartyUser> {
return executeRequest(globalErrorReceiver) {
apiCall = thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields)
}
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.internal.session.thirdparty
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser
import org.matrix.android.sdk.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.QueryMap
internal interface ThirdPartyAPI {
/**
* Get the third party server protocols.
*
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-thirdparty-protocols
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols")
fun thirdPartyProtocols(): Call<Map<String, ThirdPartyProtocol>>
/**
* Retrieve a Matrix User ID linked to a user on the third party service, given a set of user parameters.
*
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}")
fun getThirdPartyUser(@Path("protocol") protocol: String, @QueryMap params: Map<String, String>?): Call<List<ThirdPartyUser>>
}

View file

@ -0,0 +1,47 @@
/*
* 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.internal.session.thirdparty
import dagger.Binds
import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
import org.matrix.android.sdk.internal.session.SessionScope
import retrofit2.Retrofit
@Module
internal abstract class ThirdPartyModule {
@Module
companion object {
@Provides
@JvmStatic
@SessionScope
fun providesThirdPartyAPI(retrofit: Retrofit): ThirdPartyAPI {
return retrofit.create(ThirdPartyAPI::class.java)
}
}
@Binds
abstract fun bindThirdPartyService(service: DefaultThirdPartyService): ThirdPartyService
@Binds
abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
@Binds
abstract fun bindGetThirdPartyUserTask(task: DefaultGetThirdPartyUserTask): GetThirdPartyUserTask
}

View file

@ -50,6 +50,10 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
return widgetManager.getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
}
override fun getWidgetComputedUrl(widget: Widget, isLightTheme: Boolean): String? {
return widgetManager.getWidgetComputedUrl(widget, isLightTheme)
}
override fun getRoomWidgetsLive(
roomId: String,
widgetId: QueryStringValue,

View file

@ -104,6 +104,10 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
return widgetEvents.mapEventsToWidgets(widgetTypes, excludedTypes)
}
fun getWidgetComputedUrl(widget: Widget, isLightTheme: Boolean): String? {
return widgetFactory.computeURL(widget, isLightTheme)
}
private fun List<Event>.mapEventsToWidgets(widgetTypes: Set<String>? = null,
excludedTypes: Set<String>? = null): List<Widget> {
val widgetEvents = this

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.widgets.helper
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
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.sender.SenderInfo
@ -31,6 +32,7 @@ import javax.inject.Inject
internal class WidgetFactory @Inject constructor(private val userDataSource: UserDataSource,
private val realmSessionProvider: RealmSessionProvider,
private val urlResolver: ContentUrlResolver,
@UserId private val userId: String) {
fun create(widgetEvent: Event): Widget? {
@ -53,30 +55,29 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
}
}
val isAddedByMe = widgetEvent.senderId == userId
val computedUrl = widgetContent.computeURL(widgetEvent.roomId, widgetId)
return Widget(
widgetContent = widgetContent,
event = widgetEvent,
widgetId = widgetId,
senderInfo = senderInfo,
isAddedByMe = isAddedByMe,
computedUrl = computedUrl,
type = WidgetType.fromString(type)
)
}
// Ref: https://github.com/matrix-org/matrix-widget-api/blob/master/src/templating/url-template.ts#L29-L33
private fun WidgetContent.computeURL(roomId: String?, widgetId: String): String? {
var computedUrl = url ?: return null
fun computeURL(widget: Widget, isLightTheme: Boolean): String? {
var computedUrl = widget.widgetContent.url ?: return null
val myUser = userDataSource.getUser(userId)
val keyValue = data.mapKeys { "\$${it.key}" }.toMutableMap()
val keyValue = widget.widgetContent.data.mapKeys { "\$${it.key}" }.toMutableMap()
keyValue[WIDGET_PATTERN_MATRIX_USER_ID] = userId
keyValue[WIDGET_PATTERN_MATRIX_DISPLAY_NAME] = myUser?.getBestName() ?: userId
keyValue[WIDGET_PATTERN_MATRIX_AVATAR_URL] = myUser?.avatarUrl ?: ""
keyValue[WIDGET_PATTERN_MATRIX_WIDGET_ID] = widgetId
keyValue[WIDGET_PATTERN_MATRIX_ROOM_ID] = roomId ?: ""
keyValue[WIDGET_PATTERN_MATRIX_AVATAR_URL] = urlResolver.resolveFullSize(myUser?.avatarUrl) ?: ""
keyValue[WIDGET_PATTERN_MATRIX_WIDGET_ID] = widget.widgetId
keyValue[WIDGET_PATTERN_MATRIX_ROOM_ID] = widget.event.roomId ?: ""
keyValue[WIDGET_PATTERN_THEME] = getTheme(isLightTheme)
for ((key, value) in keyValue) {
computedUrl = computedUrl.replace(key, URLEncoder.encode(value.toString(), "utf-8"))
@ -84,6 +85,10 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
return computedUrl
}
private fun getTheme(isLightTheme: Boolean): String {
return if (isLightTheme) "light" else "dark"
}
companion object {
// Value to be replaced in URLS
const val WIDGET_PATTERN_MATRIX_USER_ID = "\$matrix_user_id"
@ -91,5 +96,6 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
const val WIDGET_PATTERN_MATRIX_AVATAR_URL = "\$matrix_avatar_url"
const val WIDGET_PATTERN_MATRIX_WIDGET_ID = "\$matrix_widget_id"
const val WIDGET_PATTERN_MATRIX_ROOM_ID = "\$matrix_room_id"
const val WIDGET_PATTERN_THEME = "\$theme"
}
}

View file

@ -80,4 +80,22 @@
<string name="notice_room_invite_no_invitee_by_you">O teu convite</string>
<string name="summary_you_sent_sticker">Enviaches un adhesivo.</string>
<string name="summary_you_sent_image">Enviaches unha imaxe.</string>
<string name="notice_room_server_acl_set_ip_literals_not_allowed">• Servidores con literais IP están vetados.</string>
<string name="notice_room_server_acl_set_ip_literals_allowed">• Servidores con IP literais están permitidos.</string>
<string name="notice_room_server_acl_set_banned">• Servidores con %s están vetados.</string>
<string name="notice_room_server_acl_set_allowed">• Servidores con %s están permitidos.</string>
<string name="notice_room_server_acl_set_title_by_you">Estableceches os ACLs do servidor para esta sala.</string>
<string name="notice_room_server_acl_set_title">%s estableceu os ACLs do servidor para esta sala.</string>
<string name="notice_direct_room_update">%s actualizou aquí.</string>
<string name="notice_direct_room_update_by_you">Actualizaches aquí.</string>
<string name="notice_room_update_by_you">Actualizaches esta sala.</string>
<string name="notice_room_update">%s actualizou esta sala.</string>
<string name="notice_end_to_end_by_you">Activaches o cifrado extremo-a-extremo (%1$s)</string>
<string name="notice_made_future_direct_room_visibility_by_you">Fixeches visibles as mensaxes futuras para %1$s</string>
<string name="notice_made_future_direct_room_visibility">%1$s fixo visibles as mensaxes futuras para %2$s</string>
<string name="notice_made_future_room_visibility_by_you">Fixeches visible no futuro o historial da sala para %1$s</string>
<string name="notice_ended_call_by_you">Remataches a chamada.</string>
<string name="notice_answered_call_by_you">Respondeches á chamada.</string>
<string name="notice_call_candidates_by_you">Enviaches datos para configurar a chamada.</string>
<string name="notice_call_candidates">%s enviou datos para configurar a chamada.</string>
</resources>

View file

@ -2,46 +2,46 @@
<resources>
<string name="summary_message">%1$s: %2$s</string>
<string name="summary_user_sent_image">%1$s nosūtīja attēlu.</string>
<string name="notice_room_invite_no_invitee">%s\'s uzaicinājums</string>
<string name="notice_room_invite_no_invitee">Uzaicinājums no %s</string>
<string name="notice_room_invite">%1$s uzaicināja %2$s</string>
<string name="notice_room_invite_you">%1$s uzaicināja tevi</string>
<string name="notice_room_invite_you">%1$s uzaicināja jūs</string>
<string name="notice_room_join">%1$s pievienojās</string>
<string name="notice_room_leave">%1$s atstāja</string>
<string name="notice_room_leave">%1$s pameta istabu</string>
<string name="notice_room_reject">%1$s noraidīja uzaicinājumu</string>
<string name="notice_room_kick">%1$s \"izspēra\" ārā %2$s</string>
<string name="notice_room_unban">%1$s atbanoja (atcēla pieejas liegumu) %2$s</string>
<string name="notice_room_ban">%1$s liedza pieeju (banoja) %2$s</string>
<string name="notice_room_kick">%1$s padzina %2$s</string>
<string name="notice_room_unban">%1$s atcēla pieejas liegumu %2$s</string>
<string name="notice_room_ban">%1$s liedza pieeju %2$s</string>
<string name="notice_room_withdraw">%1$s atsauca %2$s uzaicinājumu</string>
<string name="notice_avatar_url_changed">%1$s nomainīja profila attēlu</string>
<string name="notice_display_name_set">%1$s uzstādīja redzamo vārdu uz %2$s</string>
<string name="notice_display_name_changed_from">%1$s nomainīja redzamo vārdu no %2$s uz %3$s</string>
<string name="notice_display_name_removed">%1$s dzēsa savu redzamo vārdu (%2$s)</string>
<string name="notice_room_topic_changed">%1$s nomainīja tēmas nosaukumu uz: %2$s</string>
<string name="notice_room_name_changed">%1$s nomainīja istabas nosaukumu uz: %2$s</string>
<string name="notice_avatar_url_changed">%1$s nomainīja avataru</string>
<string name="notice_display_name_set">%1$s uzstādīja parādāmo vārdu uz %2$s</string>
<string name="notice_display_name_changed_from">%1$s nomainīja parādāmo vārdu no %2$s uz %3$s</string>
<string name="notice_display_name_removed">%1$s dzēsa savu parādāmo vārdu (iepriekš %2$s)</string>
<string name="notice_room_topic_changed">%1$s nomainīja tematu uz %2$s</string>
<string name="notice_room_name_changed">%1$s nomainīja istabas nosaukumu uz %2$s</string>
<string name="notice_placed_video_call">%s veica video zvanu.</string>
<string name="notice_placed_voice_call">%s veica audio zvanu.</string>
<string name="notice_answered_call">%s atbildēja zvanam.</string>
<string name="notice_answered_call">%s atbildēja uz zvanu.</string>
<string name="notice_ended_call">%s beidza zvanu.</string>
<string name="notice_made_future_room_visibility">%1$s padarīja istabas nākamo ziņu vēsturi redzamu %2$s</string>
<string name="notice_made_future_room_visibility">%1$s padarīja istabas turpmāko ziņu vēsturi redzamu %2$s</string>
<string name="notice_room_visibility_invited">visi istabas biedri no brīža, kad tika uzaicināti.</string>
<string name="notice_room_visibility_joined">visi istabas biedri no brīža, kad tika pievienojušies.</string>
<string name="notice_room_visibility_shared">visi istabas biedri.</string>
<string name="notice_room_visibility_world_readable">ikviens.</string>
<string name="notice_room_visibility_unknown">nezināms (%s).</string>
<string name="notice_end_to_end">%1$s ieslēdza ierīce-ierīce šifrēšanu (%2$s)</string>
<string name="notice_requested_voip_conference">%1$s vēlas VoIP konferenci</string>
<string name="notice_voip_started">VoIP konference sākusies</string>
<string name="notice_voip_finished">VoIP konference ir beigusies</string>
<string name="notice_avatar_changed_too">(arī profila attēls mainījās)</string>
<string name="notice_end_to_end">%1$s ieslēdza pilnīgu šifrēšanu (%2$s)</string>
<string name="notice_requested_voip_conference">%1$s pieprasīja VoIP konferenci</string>
<string name="notice_voip_started">VoIP konference sākās</string>
<string name="notice_voip_finished">VoIP konference beidzās</string>
<string name="notice_avatar_changed_too">(arī avatars tika nomainīts)</string>
<string name="notice_room_name_removed">%1$s dzēsa istabas nosaukumu</string>
<string name="notice_room_topic_removed">%1$s dzēsa istabas tēmas nosaukumu</string>
<string name="notice_profile_change_redacted">%1$s atjaunoja profila informāciju %2$s</string>
<string name="notice_room_third_party_invite">%1$s nosūtīja uzaicinājumu %2$s pievienoties istabai</string>
<string name="notice_room_third_party_registered_invite">%1$s apstiprināja uzaicinājumu priekš %2$s</string>
<string name="notice_crypto_unable_to_decrypt">** Nav iespējams atkodēt: %s **</string>
<string name="notice_crypto_error_unkwown_inbound_session_id">Sūtītāja ierīce mums nenosūtīja atslēgas priekš šīs ziņas.</string>
<string name="notice_room_topic_removed">%1$s izdzēsa istabas tematu</string>
<string name="notice_profile_change_redacted">%1$s atjaunoja savu profilu %2$s</string>
<string name="notice_room_third_party_invite">%1$s nosūtīja %2$s uzaicinājumu pievienoties istabai</string>
<string name="notice_room_third_party_registered_invite">%1$s pieņēma uzaicinājumu %2$s</string>
<string name="notice_crypto_unable_to_decrypt">** Neizdodas atšifrēt: %s **</string>
<string name="notice_crypto_error_unkwown_inbound_session_id">Sūtītāja ierīce mums nav nenosūtījusi atslēgas priekš šīs ziņas.</string>
<string name="could_not_redact">Nevarēja rediģēt</string>
<string name="unable_to_send_message">Nav iespējams nosūtīt ziņu</string>
<string name="unable_to_send_message">Neizdodas nosūtīt ziņu</string>
<string name="message_failed_to_upload">Neizdevās augšuplādēt attēlu</string>
<string name="network_error">Tīkla kļūda</string>
<string name="matrix_error">Matrix kļūda</string>
@ -58,27 +58,188 @@
<item quantity="one">%1$s un %2$d citi</item>
<item quantity="other">%1$s un %2$d citu</item>
</plurals>
<string name="notice_display_name_changed_from_by_you">Tu nomainīji savu attēlojamo vārdu no %1$s uz %2$s</string>
<string name="notice_display_name_set_by_you">Tu nomainījis savu attēlojamo vārdu uz %1$s</string>
<string name="notice_avatar_url_changed_by_you">Tu nomainīji savu avataru</string>
<string name="notice_room_withdraw_by_you">Tu atsauci %1$s uzaicinājumu</string>
<string name="notice_room_ban_by_you">Tu nobanoji %1$s</string>
<string name="notice_room_unban_by_you">Tu atbanoji %1$s</string>
<string name="notice_room_kick_by_you">Tu izspēri %1$s</string>
<string name="notice_room_reject_by_you">Tu noraidīji uzaicinājumu</string>
<string name="notice_direct_room_leave_by_you">Tu pameti telpu</string>
<string name="notice_direct_room_leave">%1$s atstāja telpu</string>
<string name="notice_room_leave_by_you">Tu atstāji telpu</string>
<string name="notice_direct_room_join_by_you">Tu pievienojies</string>
<string name="notice_direct_room_join">%1$s pievienojās telpai</string>
<string name="notice_room_join_by_you">Tu pievienojies telpai</string>
<string name="notice_room_invite_by_you">Tu uzaicināji %1$s</string>
<string name="notice_direct_room_created_by_you">Tu izveidoji apspriedi (diskusiju)</string>
<string name="notice_direct_room_created">%1$s izveidoja apspriedi (diskusiju)</string>
<string name="notice_room_created_by_you">Tu izveidoji istabu</string>
<string name="notice_room_created">%1$s izveidoja telpu</string>
<string name="notice_room_invite_no_invitee_by_you">Tavs uzaicinājums</string>
<string name="summary_you_sent_sticker">Tu nosūtīji uzlīmi/lipekli.</string>
<string name="summary_user_sent_sticker">%1$s nosūtīja uzlīmi/lipekli.</string>
<string name="summary_you_sent_image">Tu nosūtīji attēlu.</string>
<string name="notice_display_name_changed_from_by_you">Jūs nomainījāt savu parādāmo vārdu no %1$s uz %2$s</string>
<string name="notice_display_name_set_by_you">Jūs nomainījāt savu parādāmo vārdu uz %1$s</string>
<string name="notice_avatar_url_changed_by_you">Jūs nomainījāt savu avataru</string>
<string name="notice_room_withdraw_by_you">Jūs atsaucāt %1$s uzaicinājumu</string>
<string name="notice_room_ban_by_you">Jūs liedzāt pieeju %1$s</string>
<string name="notice_room_unban_by_you">Jūs atcēlāt pieejas liegumu %1$s</string>
<string name="notice_room_kick_by_you">Jūs padzināt %1$s</string>
<string name="notice_room_reject_by_you">Jūs noraidījāt uzaicinājumu</string>
<string name="notice_direct_room_leave_by_you">Jūs pametāt istabu</string>
<string name="notice_direct_room_leave">%1$s pameta istabu</string>
<string name="notice_room_leave_by_you">Jūs pametāt istabu</string>
<string name="notice_direct_room_join_by_you">Jūs pievienojāties</string>
<string name="notice_direct_room_join">%1$s pievienojās istabai</string>
<string name="notice_room_join_by_you">Jūs pievienojāties istabai</string>
<string name="notice_room_invite_by_you">Jūs uzaicinājāt %1$s</string>
<string name="notice_direct_room_created_by_you">Jūs izveidojāt diskusiju</string>
<string name="notice_direct_room_created">%1$s izveidoja diskusiju</string>
<string name="notice_room_created_by_you">Jūs izveidojāt istabu</string>
<string name="notice_room_created">%1$s izveidoja istabu</string>
<string name="notice_room_invite_no_invitee_by_you">Jūsu uzaicinājums</string>
<string name="summary_you_sent_sticker">Jūs nosūtījāt uzlīmi.</string>
<string name="summary_user_sent_sticker">%1$s nosūtīja uzlīmi.</string>
<string name="summary_you_sent_image">Jūs nosūtījāt attēlu.</string>
<string name="key_verification_request_fallback_message">%s pieprasa verificēt jūsu atslēgu, taču jūsu klients neatbalsta tērzēšanas atslēgas verifikāciju. Lai verificētu atslēgas, jums būs jāizmanto atslēgu verifikācija novecojušā veidā.</string>
<string name="notice_end_to_end_unknown_algorithm_by_you">Jūs ieslēdzāt pilnīgu šifrēšanu (neatpazīts algoritms %1$s).</string>
<string name="notice_end_to_end_unknown_algorithm">%1$s ieslēdza pilnīgu šifrēšanu (neatpazīts algoritms %2$s).</string>
<string name="notice_end_to_end_ok_by_you">Jūs ieslēdzāt pilnīgu šifrēšanu.</string>
<string name="notice_end_to_end_ok">%1$s ieslēdza pilnīgu šifrēšanu.</string>
<string name="notice_direct_room_guest_access_forbidden_by_you">Jūs esat novērsis iespēju viesiem pievienoties istabai.</string>
<string name="notice_direct_room_guest_access_forbidden">%1$s ir novērsis iespēju viesiem pievienoties istabai.</string>
<string name="notice_room_guest_access_forbidden_by_you">Jūs esat novērsis iespēju viesiem pievienoties istabai.</string>
<string name="notice_room_guest_access_forbidden">%1$s ir novērsis iespēju viesiem pievienoties istabai.</string>
<string name="notice_direct_room_guest_access_can_join_by_you">Jūs esat atļāvis viesiem pievienoties istabai.</string>
<string name="notice_direct_room_guest_access_can_join">%1$s ir atļāvis viesiem pievienoties istabai.</string>
<string name="notice_room_guest_access_can_join_by_you">Jūs esat atļāvis viesiem pievienoties istabai.</string>
<string name="notice_room_guest_access_can_join">%1$s ir atļāvis viesiem pievienoties istabai.</string>
<string name="notice_room_canonical_alias_no_change_by_you">Jūs nomainījāt adreses šai istabai.</string>
<string name="notice_room_canonical_alias_no_change">%1$s nomainīja adreses šai istabai.</string>
<string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Jūs nomainījāt galveno un alternatīvās adreses šai istabai.</string>
<string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s nomainīja galveno un alternatīvās adreses šai istabai.</string>
<string name="notice_room_canonical_alias_alternative_changed_by_you">Jūs nomainījāt alternatīvās adreses šai istabai.</string>
<string name="notice_room_canonical_alias_alternative_changed">%1$s nomainīja alternatīvās adreses šai istabai.</string>
<plurals name="notice_room_canonical_alias_alternative_removed_by_you">
<item quantity="zero">Jūs izdzēsāt šīs istabas alternatīvo adresi %1$s.</item>
<item quantity="one">Jūs izdzēsāt šīs istabas alternatīvās adreses %1$s.</item>
<item quantity="other">Jūs izdzēsāt šīs istabas alternatīvās adreses %1$s.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_removed">
<item quantity="zero">%1$s izdzēsa šīs istabas alternatīvo adresi %2$s.</item>
<item quantity="one">%1$s izdzēsa šīs istabas alternatīvās adreses %2$s.</item>
<item quantity="other">%1$s izdzēsa šīs istabas alternatīvās adreses %2$s.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_added_by_you">
<item quantity="zero">Jūs pievienojāt šīs istabas alternatīvo adresi %1$s.</item>
<item quantity="one">Jūs pievienojāt šīs istabas alternatīvās adreses %1$s.</item>
<item quantity="other">Jūs pievienojāt šīs istabas alternatīvās adreses %1$s.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_added">
<item quantity="zero">%1$s pievienoja šīs istabas alternatīvo adresi %2$s.</item>
<item quantity="one">%1$s pievienoja šīs istabas alternatīvās adreses %2$s.</item>
<item quantity="other">%1$s pievienoja šīs istabas alternatīvās adreses %2$s.</item>
</plurals>
<string name="notice_room_canonical_alias_unset_by_you">Jūs izdzēsāt šis istabas galveno adresi.</string>
<string name="notice_room_canonical_alias_unset">%1$s izdzēsa šis istabas galveno adresi.</string>
<string name="notice_room_canonical_alias_set_by_you">Jūs iestatījāt %1$s kā šis istabas galveno adresi.</string>
<string name="notice_room_canonical_alias_set">%1$s iestatīja %2$s kā šis istabas galveno adresi.</string>
<string name="notice_room_aliases_added_and_removed_by_you">Jūs pievienojāt %1$s un izdzēsāt %2$s kā šīs istabas adreses.</string>
<string name="notice_room_aliases_added_and_removed">%1$s pievienoja %2$s un izdzēsa %3$s kā šīs istabas adreses.</string>
<plurals name="notice_room_aliases_removed_by_you">
<item quantity="zero">Jūs izdzēsāt %1$s kā šīs istabas adresi.</item>
<item quantity="one">Jūs izdzēsāt %1$s kā šīs istabas adreses.</item>
<item quantity="other">Jūs izdzēsāt %1$s kā šīs istabas adreses.</item>
</plurals>
<plurals name="notice_room_aliases_removed">
<item quantity="zero">%1$s izdzēsa %2$s kā šīs istabas adresi.</item>
<item quantity="one">%1$s izdzēsa %2$s kā šīs istabas adreses.</item>
<item quantity="other">%1$s izdzēsa %2$s kā šīs istabas adreses.</item>
</plurals>
<plurals name="notice_room_aliases_added_by_you">
<item quantity="zero">Jūs pievienojāt %1$s kā šīs istabas adresi.</item>
<item quantity="one">Jūs pievienojāt %1$s kā šīs istabas adreses.</item>
<item quantity="other">Jūs pievienojāt %1$s kā šīs istabas adreses.</item>
</plurals>
<plurals name="notice_room_aliases_added">
<item quantity="zero">%1$s pievienoja %2$s kā šīs istabas adresi.</item>
<item quantity="one">%1$s pievienoja %2$s kā šis istabas adreses.</item>
<item quantity="other">%1$s pievienoja %2$s kā šīs istabas adreses.</item>
</plurals>
<string name="notice_room_withdraw_with_reason_by_you">Jūs atsaucāt %1$s uzaicinājumu. Iemesls: %2$s</string>
<string name="notice_room_withdraw_with_reason">%1$s atsauca uzaicinājumu %2$s. Iemesls: %3$s</string>
<string name="notice_room_third_party_registered_invite_with_reason_by_you">Jūs pieņēmāt uzaicinājumu %1$s. Iemesls: %2$s</string>
<string name="notice_room_third_party_registered_invite_with_reason">%1$s pieņēma uzaicinājumu %2$s. Iemesls: %3$s</string>
<string name="notice_room_third_party_revoked_invite_with_reason_by_you">Jūs atsaucāt uzaicinājumu %1$s pievienoties istabai. Iemesls: %2$s</string>
<string name="notice_room_third_party_revoked_invite_with_reason">%1$s atsauca uzaicinājumu %2$s pievienoties istabai. Iemesls: %3$s</string>
<string name="notice_room_third_party_invite_with_reason_by_you">Jūs nosūtījāt uzaicinājumu %1$s pievienoties istabai. Iemesls: %2$s</string>
<string name="notice_room_third_party_invite_with_reason">%1$s nosūtīja uzaicinājumu %2$s pievienoties istabai. Iemesls: %3$s</string>
<string name="notice_room_ban_with_reason_by_you">Jūs liedzāt pieeju %1$s. Iemesls: %2$s</string>
<string name="notice_room_ban_with_reason">%1$s liedza pieeju %2$s. Iemesls: %3$s</string>
<string name="notice_room_unban_with_reason_by_you">Jūs atcēlāt pieejas liegumu %1$s. Iemesls: %2$s</string>
<string name="notice_room_unban_with_reason">%1$s atcēla %2$s pieejas liegumu. Iemesls: %3$s</string>
<string name="notice_room_kick_with_reason_by_you">Jūs padzināt %1$s. Iemesls: %2$s</string>
<string name="notice_room_kick_with_reason">%1$s padzina %2$s. Iemesls: %3$s</string>
<string name="notice_room_reject_with_reason_by_you">Jūs noraidījāt uzaicinājumu. Iemesls: %1$s</string>
<string name="notice_room_reject_with_reason">%1$s noraidīja uzaicinājumu. Iemesls: %2$s</string>
<string name="notice_direct_room_leave_with_reason_by_you">Jūs izgājāt. Iemesls: %1$s</string>
<string name="notice_direct_room_leave_with_reason">%1$s izgāja. Iemels: %2$s</string>
<string name="notice_room_leave_with_reason_by_you">Jūs pametāt istabu. Iemesls: %1$s</string>
<string name="notice_room_leave_with_reason">%1$s pameta istabu. Iemesls: %2$s</string>
<string name="notice_direct_room_join_with_reason_by_you">Jūs pievienojāties. Iemesls: %1$s</string>
<string name="notice_direct_room_join_with_reason">%1$s pievienojās. Iemesls: %2$s</string>
<string name="notice_room_join_with_reason_by_you">Jūs pievienojāties istabai. Iemesls: %1$s</string>
<string name="notice_room_join_with_reason">%1$s pievienojās istabai. Iemesls: %2$s</string>
<string name="notice_room_invite_you_with_reason">%1$s uzaicināja jūs. Iemesls: %2$s</string>
<string name="notice_room_invite_with_reason_by_you">Jūs uzaicinājāt %1$s. Iemesls: %2$s</string>
<string name="notice_room_invite_with_reason">%1$s uzaicināja %2$s. Iemesls: %3$s</string>
<string name="notice_room_invite_no_invitee_with_reason_by_you">Jūsu uzaicinājums. Iemesls: %1$s</string>
<string name="notice_room_invite_no_invitee_with_reason">%1$s uzaicinājums. Iemesls: %2$s</string>
<string name="clear_timeline_send_queue">Notīrīt sūtīšanas rindu</string>
<string name="event_status_sending_message">Sūta ziņu…</string>
<string name="initial_sync_start_importing_account_data">Sākotnējā sinhronizācija:
\nImportē konta datus</string>
<string name="initial_sync_start_importing_account_groups">Sākotnējā sinhronizācija:
\nImportē kopienas</string>
<string name="initial_sync_start_importing_account_left_rooms">Sākotnējā sinhronizācija:
\nImportē pamestās istabas</string>
<string name="initial_sync_start_importing_account_invited_rooms">Sākotnējā sinhronizācija:
\nImportē istabas, uz kurām uzaicināts</string>
<string name="initial_sync_start_importing_account_joined_rooms">Sākotnējā sinhronizācija:
\nImportē istabas, kurās ieiets</string>
<string name="initial_sync_start_importing_account_rooms">Sākotnējā sinhronizācija:
\nImportē istabas</string>
<string name="initial_sync_start_importing_account_crypto">Sākotnējā sinhronizācija:
\nImportē kriptogrāfiju</string>
<string name="initial_sync_start_importing_account">Sākotnējā sinhronizācija:
\nImportē kontu…</string>
<string name="room_displayname_empty_room_was">Tukša istaba (bija %s)</string>
<plurals name="room_displayname_four_and_more_members">
<item quantity="zero">%1$s, %2$s, %3$s un %4$d citi</item>
<item quantity="one">%1$s, %2$s, %3$s un %4$d cits</item>
<item quantity="other">%1$s, %2$s, %3$s un %4$d citi</item>
</plurals>
<string name="room_displayname_4_members">%1$s, %2$s, %3$s un %4$s</string>
<string name="room_displayname_3_members">%1$s, %2$s un %3$s</string>
<string name="notice_power_level_diff">%1$s no %2$s uz %3$s</string>
<string name="notice_power_level_changed">%1$s nomainīja %2$s pieejas līmeni.</string>
<string name="notice_power_level_changed_by_you">Jūs nomainījāt %1$s pieejas līmeni.</string>
<string name="power_level_custom_no_value">Pielāgots</string>
<string name="power_level_custom">Pielāgots (%1$d)</string>
<string name="power_level_default">Noklusējuma</string>
<string name="power_level_moderator">Moderators</string>
<string name="power_level_admin">Administrators</string>
<string name="notice_room_third_party_registered_invite_by_you">Jūs pieņēmāt uzaicinājumu %1$s</string>
<string name="notice_direct_room_third_party_revoked_invite_by_you">Jūs atsaucāt uzaicinājumu %1$s</string>
<string name="notice_direct_room_third_party_revoked_invite">%1$s atsauca uzaicinājumu %2$s</string>
<string name="notice_room_third_party_revoked_invite_by_you">Jūs atsaucāt uzaicinājumu %1$s pievienoties istabai</string>
<string name="notice_room_third_party_revoked_invite">%1$s atsauca uzaicinājumu %2$s pievienoties istabai</string>
<string name="notice_direct_room_third_party_invite_by_you">Jūs uzaicinājāt %1$s</string>
<string name="notice_direct_room_third_party_invite">%1$s uzaicināja %2$s</string>
<string name="notice_room_third_party_invite_by_you">Jūs nosūtījāt %1$s uzaicinājumu pievienoties istabai</string>
<string name="notice_profile_change_redacted_by_you">Jūs atjaunojāt savu profilu %1$s</string>
<string name="notice_event_redacted_by_with_reason">%1$s izdzēsa ziņu [iemesls: %2$s]</string>
<string name="notice_event_redacted_with_reason">Ziņa izdzēsta [iemesls: %1$s]</string>
<string name="notice_event_redacted_by">%1$s izdzēsa ziņu</string>
<string name="notice_event_redacted">Ziņa izdzēsta</string>
<string name="notice_room_avatar_removed_by_you">Jūs izdzēsāt istabas avataru</string>
<string name="notice_room_avatar_removed">%1$s izdzēsa istabas avataru</string>
<string name="notice_room_topic_removed_by_you">Jūs izdzēsāt istabas tematu</string>
<string name="notice_room_name_removed_by_you">Jūs dzēsāt istabas nosaukumu</string>
<string name="notice_requested_voip_conference_by_you">Jūs pieprasījāt VoIP konferenci</string>
<string name="notice_end_to_end_by_you">Jūs ieslēdzāt pilnīgu šifrēšanu (%1$s)</string>
<string name="notice_made_future_direct_room_visibility_by_you">Jūs padarījāt turpmākās ziņas redzamas %1$s</string>
<string name="notice_made_future_direct_room_visibility">%1$s padarīja turpmākās ziņas redzamas %2$s</string>
<string name="notice_made_future_room_visibility_by_you">Jūs padarījāt istabas turpmāko ziņu vēsturi redzamu %1$s</string>
<string name="notice_ended_call_by_you">Jūs beidzāt zvanu.</string>
<string name="notice_answered_call_by_you">Jūs atbildējāt uz zvanu.</string>
<string name="notice_call_candidates_by_you">Jūs nosūtījāt datus zvana uzsākšanai.</string>
<string name="notice_call_candidates">%s nosūtīja datus zvana uzsākšanai.</string>
<string name="notice_placed_voice_call_by_you">Jūs veicāt balss zvanu.</string>
<string name="notice_placed_video_call_by_you">Jūs veicāt video zvanu.</string>
<string name="notice_room_name_changed_by_you">Jūs nomainījāt istabas nosaukumu uz %1$s</string>
<string name="notice_room_avatar_changed_by_you">Jūs nomainījāt istabas avataru</string>
<string name="notice_room_avatar_changed">%1$s nomainīja istabas avataru</string>
<string name="notice_room_topic_changed_by_you">Jūs nomainījāt tematu uz %1$s</string>
<string name="notice_display_name_removed_by_you">Jūs dzēsāt savu parādāmo vārdu (iepriekš %1$s)</string>
</resources>

View file

@ -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===85
enum class===88
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -25,8 +25,8 @@ cd jitsi-meet
# This is commit after version 2.2.2, which does not compile
# git checkout 5a934c071a5cbe64de275a25d0ed62d8193cdd03
# Version android-sdk-2.9.3, commit abcbbbea12e3ef88012b14723bb8cd42dbefc988
git checkout android-sdk-2.9.3
# Version android-sdk-3.1.0, commit 7a64bf006ea027b77564d8847570e1ac46ff0ec0
git checkout android-sdk-3.1.0
echo
echo "##################################################"

View file

@ -114,6 +114,9 @@ android {
targetSdkVersion 30
multiDexEnabled true
renderscriptTargetApi 24
renderscriptSupportModeEnabled true
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode.
// Other branches (master, features, etc.) will have version code based on application version.
versionCode project.getVersionCode()
@ -232,7 +235,7 @@ android {
productFlavors {
gplay {
dimension "store"
isDefault = true
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
resValue "bool", "isGplay", "true"
@ -319,6 +322,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.sharetarget:sharetarget:1.0.0"
implementation 'androidx.core:core-ktx:1.3.2'
implementation "androidx.media:media:1.2.1"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0"
@ -373,6 +377,7 @@ dependencies {
implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:1.1.1'
implementation "androidx.autofill:autofill:$autofill_version"
implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
// Custom Tab
@ -430,7 +435,10 @@ dependencies {
// WebRTC
// org.webrtc:google-webrtc is for development purposes only
// implementation 'org.webrtc:google-webrtc:1.0.+'
implementation('org.jitsi.react:jitsi-meet-sdk:2.9.3') { transitive = true }
implementation('com.facebook.react:react-native-webrtc:1.87.3-jitsi-6624067@aar')
// Jitsi
implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0')
// QR-code
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
@ -441,6 +449,8 @@ dependencies {
implementation 'com.vanniktech:emoji-material:0.7.0'
implementation 'com.vanniktech:emoji-google:0.7.0'
implementation 'im.dlg:android-dialer:1.2.5'
// TESTS
testImplementation 'junit:junit:4.13'
testImplementation "org.amshove.kluent:kluent-android:$kluent_version"

View file

@ -277,8 +277,23 @@ class UiAllScreensSanityTest {
assertDisplayed(R.id.roomProfileAvatarView)
// Leave
// Room addresses
clickListItem(R.id.matrixProfileRecyclerView, 13)
onView(isRoot()).perform(waitForView(withText(R.string.room_alias_published_alias_title)))
pressBack()
// Room permissions
clickListItem(R.id.matrixProfileRecyclerView, 15)
onView(isRoot()).perform(waitForView(withText(R.string.room_permissions_title)))
clickOn(R.string.room_permissions_change_room_avatar)
clickDialogNegativeButton()
// Toggle
clickOn(R.string.show_advanced)
clickOn(R.string.hide_advanced)
pressBack()
// Leave
clickListItem(R.id.matrixProfileRecyclerView, 17)
clickDialogNegativeButton()
// Menu share
@ -289,27 +304,12 @@ class UiAllScreensSanityTest {
}
private fun navigateToRoomParameters() {
// Room addresses
clickListItem(R.id.roomSettingsRecyclerView, 4)
onView(isRoot()).perform(waitForView(withText(R.string.room_alias_published_alias_title)))
pressBack()
// Room permissions
clickListItem(R.id.roomSettingsRecyclerView, 6)
onView(isRoot()).perform(waitForView(withText(R.string.room_permissions_title)))
clickOn(R.string.room_permissions_change_room_avatar)
clickDialogNegativeButton()
// Toggle
clickOn(R.string.show_advanced)
clickOn(R.string.hide_advanced)
pressBack()
// Room history readability
clickListItem(R.id.roomSettingsRecyclerView, 8)
clickListItem(R.id.roomSettingsRecyclerView, 4)
pressBack()
// Room access
clickListItem(R.id.roomSettingsRecyclerView, 10)
clickListItem(R.id.roomSettingsRecyclerView, 6)
pressBack()
}

Some files were not shown because too many files have changed in this diff Show more