diff --git a/CHANGES.rst b/CHANGES.rst index 6d40ede8b..3b357dc9a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,17 @@ +Changes to Matrix Android SDK in 0.9.33 (2020-02-10) +======================================================= + +Features: + - HTTP Proxy configuration using HomeServerConnectionConfig + +Bugfix: + - Fix bad constant values for homeserver CS api versions + - Ensure custom fields are sent for `m.room.message` Events (#515) + - Fix issue of blocked UI after a video call (#496, vector-im/riot-android#3311) + +Others: + - Cleanup in org.matrix.androisdk.call + Changes to Matrix Android SDK in 0.9.32 (2019-11-25) ======================================================= @@ -1628,7 +1642,7 @@ Features: ======================================================= -Changes to Matrix Android SDK in 0.9.X (2019-XX-XX) +Changes to Matrix Android SDK in 0.9.X (2020-XX-XX) ======================================================= Features: diff --git a/matrix-sdk-core/src/main/java/org/matrix/androidsdk/HomeServerConnectionConfig.java b/matrix-sdk-core/src/main/java/org/matrix/androidsdk/HomeServerConnectionConfig.java index 14bea07e9..92980f5eb 100644 --- a/matrix-sdk-core/src/main/java/org/matrix/androidsdk/HomeServerConnectionConfig.java +++ b/matrix-sdk-core/src/main/java/org/matrix/androidsdk/HomeServerConnectionConfig.java @@ -18,9 +18,10 @@ package org.matrix.androidsdk; import android.net.Uri; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.text.TextUtils; import org.json.JSONArray; import org.json.JSONException; @@ -29,6 +30,8 @@ import org.matrix.androidsdk.rest.model.login.Credentials; import org.matrix.androidsdk.ssl.Fingerprint; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.util.ArrayList; import java.util.List; @@ -61,6 +64,11 @@ public class HomeServerConnectionConfig { private boolean mShouldAcceptTlsExtensions = true; // Force usage of TLS versions private boolean mForceUsageTlsVersions; + // the proxy hostname + private String mProxyHostname; + // the proxy port + private int mProxyPort = -1; + /** * Private constructor. Please use the Builder @@ -160,7 +168,7 @@ public void setCredentials(Credentials credentials) { /** * @return whether we should reject X509 certs that were issued by trusts CAs and only trust - * certs with matching fingerprints. + * certs with matching fingerprints. */ public boolean shouldPin() { return mPin; @@ -196,6 +204,21 @@ public boolean forceUsageOfTlsVersions() { return mForceUsageTlsVersions; } + + /** + * @return proxy config if available + */ + @Nullable + public Proxy getProxyConfig() { + if (mProxyHostname == null || mProxyHostname.length() == 0 || mProxyPort == -1) { + return null; + } + + return new Proxy(Proxy.Type.HTTP, + new InetSocketAddress(mProxyHostname, mProxyPort)); + } + + @Override public String toString() { return "HomeserverConnectionConfig{" + @@ -206,6 +229,8 @@ public String toString() { ", mCredentials=" + mCredentials + ", mPin=" + mPin + ", mShouldAcceptTlsExtensions=" + mShouldAcceptTlsExtensions + + ", mProxyHostname=" + (null == mProxyHostname ? "" : mProxyHostname) + + ", mProxyPort=" + (-1 == mProxyPort ? "" : mProxyPort) + ", mTlsVersions=" + (null == mTlsVersions ? "" : mTlsVersions.size()) + ", mTlsCipherSuites=" + (null == mTlsCipherSuites ? "" : mTlsCipherSuites.size()) + '}'; @@ -267,6 +292,14 @@ public JSONObject toJson() throws JSONException { json.put("tls_cipher_suites", new JSONArray(tlsCipherSuites)); } + if (mProxyPort != -1) { + json.put("proxy_port", mProxyPort); + } + + if (mProxyHostname != null && mProxyHostname.length() > 0) { + json.put("proxy_hostname", mProxyHostname); + } + return json; } @@ -323,6 +356,11 @@ public static HomeServerConnectionConfig fromJson(JSONObject jsonObject) throws } } + // Set the proxy options right if any + if (jsonObject.has("proxy_hostname") && jsonObject.has("proxy_port")) { + builder.withProxy(jsonObject.getString("proxy_hostname"), jsonObject.getInt("proxy_port")); + } + return builder.build(); } @@ -549,6 +587,17 @@ public Builder withTlsLimitations(boolean tlsLimitations, boolean enableCompatib return this; } + /** + * @param proxyHostname Proxy Hostname + * @param proxyPort Proxy Port + * @return this builder + */ + public Builder withProxy(@Nullable String proxyHostname, int proxyPort) { + mHomeServerConnectionConfig.mProxyHostname = proxyHostname; + mHomeServerConnectionConfig.mProxyPort = proxyPort; + return this; + } + /** * @return the {@link HomeServerConnectionConfig} */ diff --git a/matrix-sdk-core/src/main/java/org/matrix/androidsdk/RestClientHttpClientFactory.kt b/matrix-sdk-core/src/main/java/org/matrix/androidsdk/RestClientHttpClientFactory.kt index b8e7b0142..78311c584 100644 --- a/matrix-sdk-core/src/main/java/org/matrix/androidsdk/RestClientHttpClientFactory.kt +++ b/matrix-sdk-core/src/main/java/org/matrix/androidsdk/RestClientHttpClientFactory.kt @@ -43,6 +43,7 @@ class RestClientHttpClientFactory(private val testInterceptor: Interceptor? = nu .connectTimeout(RestClient.CONNECTION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS) .readTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) .writeTimeout(WRITE_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .proxy(hsConfig.proxyConfig) .addInterceptor(authenticationInterceptor) if (BuildConfig.DEBUG) { diff --git a/matrix-sdk/build.gradle b/matrix-sdk/build.gradle index b9fda80e4..4ee93b8a9 100644 --- a/matrix-sdk/build.gradle +++ b/matrix-sdk/build.gradle @@ -27,8 +27,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 932 - versionName "0.9.32" + versionCode 933 + versionName "0.9.33" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // Enable multi dex for test diff --git a/matrix-sdk/src/androidTest/java/org/matrix/androidsdk/event/SendCustomEventTest.kt b/matrix-sdk/src/androidTest/java/org/matrix/androidsdk/event/SendCustomEventTest.kt new file mode 100644 index 000000000..cb79d74b1 --- /dev/null +++ b/matrix-sdk/src/androidTest/java/org/matrix/androidsdk/event/SendCustomEventTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.matrix.androidsdk.event + +import android.text.TextUtils +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import com.google.gson.JsonParser +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.matrix.androidsdk.common.CommonTestHelper +import org.matrix.androidsdk.common.CryptoTestHelper +import org.matrix.androidsdk.common.TestApiCallback +import org.matrix.androidsdk.common.TestConstants +import org.matrix.androidsdk.core.Log +import org.matrix.androidsdk.data.RoomState +import org.matrix.androidsdk.listeners.MXEventListener +import org.matrix.androidsdk.rest.model.Event +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SendCustomEventTest { + private val mTestHelper = CommonTestHelper() + private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + @Test + fun test01_sendCustomEvent() { + Log.e(LOG_TAG, "test01_sendEvent") + val context = InstrumentationRegistry.getContext() + val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, mCryptoTestHelper.defaultSessionParams) + var roomId: String? = null + val lock1 = CountDownLatch(1) + bobSession.createRoom(object : TestApiCallback(lock1) { + override fun onSuccess(info: String) { + roomId = info + super.onSuccess(info) + } + }) + mTestHelper.await(lock1) + assertNotNull(roomId) + val room = bobSession.dataHandler.getRoom(roomId!!) + // Wait for the event + var receivedEvent: Event? = null + val lock3 = CountDownLatch(1) + bobSession.dataHandler.addListener(object : MXEventListener() { + override fun onLiveEvent(event: Event, roomState: RoomState) { + if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { + receivedEvent = event + lock3.countDown() + } + } + }) + // Send event + val parser = JsonParser() + val element = parser.parse("{" + + "\"body\" : \"message body\"," + + "\"msgtype\" : \"m.text\"," + + "\"mirrorIdKey\" : \"customValue\"" + + "}") + val content = element.asJsonObject + val event = Event(Event.EVENT_TYPE_MESSAGE, content, bobSession.myUserId, roomId) + val lock2 = CountDownLatch(1) + room.sendEvent(event, TestApiCallback(lock2)) + mTestHelper.await(lock2) + // Wait for the callback + mTestHelper.await(lock3) + + assertNotNull(receivedEvent) + assertEquals("message body", receivedEvent!!.content.asJsonObject.get("body")?.asString) + assertEquals("m.text", receivedEvent!!.content.asJsonObject.get("msgtype")?.asString) + assertEquals("customValue", receivedEvent!!.content.asJsonObject.get("mirrorIdKey")?.asString) + + bobSession.clear(context) + } + + companion object { + private const val LOG_TAG = "EventTest" + } +} \ No newline at end of file diff --git a/matrix-sdk/src/main/assets/www/androidcall.js b/matrix-sdk/src/main/assets/www/androidcall.js deleted file mode 100755 index 02466d0e1..000000000 --- a/matrix-sdk/src/main/assets/www/androidcall.js +++ /dev/null @@ -1,91 +0,0 @@ -"use strict"; -console.log("Loading call"); -var BASE_URL = "https://matrix.org"; -var mxCall; - -// debug tools -function showToast(toast) { - Android.showToast(toast); -} - -// initializers -function getRoomId() { - return Android.wgetRoomId(); -} - -function getCallId() { - return Android.wgetCallId(); -} - -// call when the webview is loaded -function onLoaded() { - return Android.wOnLoaded(); -} - -// start call methods -function placeVoiceCall() { - mxCall.placeVoiceCall( - document.getElementById("remote"), - document.getElementById("self") - ); -} - -function placeVideoCall() { - mxCall.placeVideoCall( - document.getElementById("remote"), - document.getElementById("self") - ); -} - -function setCallId(callId) { - mxCall.callId = callId; -} - -function initWithInvite(callId, msg) { - mxCall.localVideoElement = document.getElementById("self"); - mxCall.remoteVideoElement = document.getElementById("remote"); - mxCall.callId = callId; - mxCall._initWithInvite(msg); -} - -// receive candidate -function gotRemoteCandidate(candidate) { - mxCall._gotRemoteIceCandidate(candidate); -} - -// receive candidates -function gotRemoteCandidates(candidates) { - for (var i = 0; i < candidates.length; i++) { - mxCall._gotRemoteIceCandidate(candidates[i]); - } -} - - -// the user accept the call -function answerCall() { - mxCall.answer(); -} - -// the callee accepts the call -function receivedAnswer(msg) { - mxCall._receivedAnswer(msg); -} - -// hangup methods -function onHangupReceived(msg) { - mxCall._onHangupReceived(msg); -} - -function hangup() { - mxCall.hangup("", false); -} - -function onAnsweredElsewhere() { - mxCall._onAnsweredElsewhere(); -} - -window.onload = function() { - mxCall = createNewMatrixCall(getRoomId()); - mxCall.callId = getCallId(); - onLoaded(); -}; diff --git a/matrix-sdk/src/main/assets/www/call.html b/matrix-sdk/src/main/assets/www/call.html deleted file mode 100755 index 113bdd505..000000000 --- a/matrix-sdk/src/main/assets/www/call.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - -
- -
-
- -
- - diff --git a/matrix-sdk/src/main/assets/www/call.js b/matrix-sdk/src/main/assets/www/call.js deleted file mode 100755 index ace7a368f..000000000 --- a/matrix-sdk/src/main/assets/www/call.js +++ /dev/null @@ -1,929 +0,0 @@ -"use strict"; -/** - * This is an internal module. See {@link createNewMatrixCall} for the public API. - * @module webrtc/call - */ -var DEBUG = false; // set true to enable console logging. - -// events: hangup, error, replaced - -function androidLog(message) { - Android.wlog(message); -} - -function AndroidCallError(message) { - Android.wCallError(message); -} - -function AndroidSendEvent(roomId, event, content) { - Android.wSendEvent(roomId, event, content); -} - -function AndroidOnStateUpdate(state) { - Android.wOnStateUpdate(state); -} - -function AndroidGetTurnServer() { - return Android.wgetTurnServer(); -} - -/** - * Construct a new Matrix Call. - * @constructor - * @param {Object} opts Config options. - * @param {string} opts.roomId The room ID for this call. - * @param {Object} opts.webRtc The WebRTC globals from the browser. - * @param {Object} opts.URL The URL global. - * @param {Array} opts.turnServers Optional. A list of TURN servers. - * @param {MatrixClient} opts.client The Matrix Client instance to send events to. - */ -function MatrixCall(opts) { - this.roomId = opts.roomId; - /*this.client = opts.client;*/ - this.webRtc = opts.webRtc; - this.URL = opts.URL; - // Array of Objects with urls, username, credential keys - this.turnServers = opts.turnServers || [{ - urls: [MatrixCall.FALLBACK_STUN_SERVER] - }]; - - this.callId = "c" + new Date().getTime(); - this.state = 'fledgling'; - this.didConnect = false; -} - -/** The length of time a call can be ringing for. */ -MatrixCall.CALL_TIMEOUT_MS = 120000; -/** The fallback server to use for STUN. */ -MatrixCall.FALLBACK_STUN_SERVER = ''; - -/** - * update the matrix call state - */ -MatrixCall.prototype.updateState = function(state) { - this.state = state; - - AndroidOnStateUpdate(state); - - // the sound has been muted until the connection is established - if (this.state == 'connected') { - if (this.localAVStream) { - var audioTracks = this.localAVStream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = true; - } - } - - if (this.remoteAVStream) { - var audioTracks = this.remoteAVStream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = true; - } - } - - // the local preview is displayed in fullscren until the connection is established - this.getLocalVideoElement().parentElement.style.top = "5%"; - this.getLocalVideoElement().parentElement.style.left = "5%"; - this.getLocalVideoElement().parentElement.style.width = "25%"; - this.getLocalVideoElement().parentElement.style.height = "25%"; - - _tryPlayRemoteStream(this); - } - - if (this.getRemoteVideoElement()) { - if (this.state == 'connected') { - this.getRemoteVideoElement().style.display = 'block'; - } else { - this.getRemoteVideoElement().style.display = 'none'; - } - } -} - -/** - * Place a voice call to this room. - * @throws If you have not specified a listener for 'error' events. - */ -MatrixCall.prototype.placeVoiceCall = function(remoteVideoElement, localVideoElement) { - androidLog("placeVoiceCall"); - - // required to have the callee sound - // else the received stream is never started - this.remoteVideoElement = remoteVideoElement; - - _placeCallWithConstraints(this, _getUserMediaVideoContraints('voice')); - this.type = 'voice'; -}; - -/** - * Place a video call to this room. - * @param {Element} remoteVideoElement a <video> DOM element - * to render video to. - * @param {Element} localVideoElement a <video> DOM element - * to render the local camera preview. - * @throws If you have not specified a listener for 'error' events. - */ -MatrixCall.prototype.placeVideoCall = function(remoteVideoElement, localVideoElement) { - androidLog("placeVideoCall"); - - this.localVideoElement = localVideoElement; - this.remoteVideoElement = remoteVideoElement; - - _placeCallWithConstraints(this, _getUserMediaVideoContraints('video')); - this.type = 'video'; -}; - -/** - * Retrieve the local <video> DOM element. - * @return {Element} The dom element - */ -MatrixCall.prototype.getLocalVideoElement = function() { - return this.localVideoElement; -}; - -/** - * Retrieve the remote <video> DOM element. - * @return {Element} The dom element - */ -MatrixCall.prototype.getRemoteVideoElement = function() { - return this.remoteVideoElement; -}; - -/** - * Set the remote <video> DOM element. If this call is active, - * video will be rendered to it immediately. - * @param {Element} element The <video> DOM element. - */ -MatrixCall.prototype.setRemoteVideoElement = function(element) { - this.remoteVideoElement = element; -}; - -/** - * Configure this call from an invite event. Used by MatrixClient. - * @protected - * @param {MatrixEvent} event The m.call.invite event - */ -MatrixCall.prototype._initWithInvite = function(msg) { - - this.msg = msg; - this.peerConn = _createPeerConnection(this); - var self = this; - if (this.peerConn) { - this.peerConn.setRemoteDescription( - new this.webRtc.RtcSessionDescription(this.msg.offer), - hookCallback(self, self._onSetRemoteDescriptionSuccess), - hookCallback(self, self._onSetRemoteDescriptionError) - ); - } - - this.updateState('wait_local_media'); - this.direction = 'inbound'; - - if (self.getLocalVideoElement()) { - self.getLocalVideoElement().style.display = 'none'; - } - - if (self.getRemoteVideoElement()) { - self.getRemoteVideoElement().style.display = 'none'; - } - - // firefox and OpenWebRTC's RTCPeerConnection doesn't add streams until it - // starts getting media on them so we need to figure out whether a video - // channel has been offered by ourselves. - if (this.msg.offer.sdp.indexOf('m=video') > -1) { - this.type = 'video'; - } - else { - this.type = 'voice'; - } - - this.webRtc.getUserMedia( - _getUserMediaVideoContraints(this.type), - hookCallback(self, self._gotUserMediaForIncomingCall), - hookCallback(self, self._getUserMediaFailed) - ); -}; - -/** - * Configure this call from a hangup event. Used by MatrixClient. - * @protected - * @param {MatrixEvent} event The m.call.hangup event - */ -MatrixCall.prototype._initWithHangup = function(event) { - // perverse as it may seem, sometimes we want to instantiate a call with a - // hangup message (because when getting the state of the room on load, events - // come in reverse order and we want to remember that a call has been hung up) - this.msg = event.getContent(); - this.updateState('ended'); -}; - -/** - * Answer a call. - */ -MatrixCall.prototype.answer = function() { - debuglog("Answering call " + this.callId); - var self = this; - - // audio case : do not wait after local AV - if (!this.localAVStream && !this.waitForLocalAVStream) { - this.updateState('wait_local_media'); - } else if (this.localAVStream) { - self._create_answer(); - } -}; - -/** - * Hangup a call. - * @param {string} reason The reason why the call is being hung up. - * @param {boolean} suppressEvent True to suppress emitting an event. - */ -MatrixCall.prototype.hangup = function(reason, suppressEvent) { - debuglog("Ending call " + this.callId); - terminate(this, "local", reason, !suppressEvent); - var content = { - version: 0, - call_id: this.callId, - reason: reason - }; - sendEvent(this, 'm.call.hangup', content); -}; - -/** - * Internal - * @private - * @param {Object} stream - */ -MatrixCall.prototype._gotUserMediaForInvite = function(stream) { - debuglog("_gotUserMediaForInvite"); - - if (this.successor) { - this.successor._gotUserMediaForAnswer(stream); - return; - } - if (this.state == 'ended') { - return; - } - var self = this; - var videoEl = this.getLocalVideoElement(); - - if (videoEl && this.type == 'video') { - videoEl.autoplay = true; - videoEl.src = this.URL.createObjectURL(stream); - videoEl.muted = true; - setTimeout(function() { - var vel = self.getLocalVideoElement(); - if (vel.play) { - vel.play(); - } - }, 0); - } - - - this.localAVStream = stream; - - // mute the sound until the call is connected - var audioTracks = stream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = false; - } - - this.peerConn = _createPeerConnection(this); - this.peerConn.addStream(stream); - this.peerConn.createOffer( - hookCallback(self, self._gotLocalOffer), - hookCallback(self, self._getLocalOfferFailed) - ); - - this.updateState('create_offer'); -}; - - -/** - * Internal - * @private - * @param {Object} stream - */ -MatrixCall.prototype._gotUserMediaForIncomingCall = function(stream) { - debuglog("_gotUserMediaForInbound"); - - var self = this; - if (self.state == 'ended') { - return; - } - var localVidEl = self.getLocalVideoElement(); - - localVidEl.style.display = 'block'; - - if (localVidEl) { - localVidEl.autoplay = true; - localVidEl.src = self.URL.createObjectURL(stream); - localVidEl.muted = self; - setTimeout(function() { - var vel = self.getLocalVideoElement(); - if (vel.play) { - vel.play(); - } - }, 0); - } - - self.localAVStream = stream; - - // mute the incoming sound until the connection is established - var audioTracks = stream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = false; - } - self.peerConn.addStream(stream); - - this.updateState('ringing'); -} - -MatrixCall.prototype._create_answer = function() { - var self = this; - if (self.state == 'ended') { - return; - } - - var constraints = { - 'mandatory': { - 'OfferToReceiveAudio': true, - 'OfferToReceiveVideo': self.type == 'video' - }, - }; - self.peerConn.createAnswer(function(description) { - debuglog("Created answer: " + description); - self.peerConn.setLocalDescription(description, function() { - var content = { - version: 0, - call_id: self.callId, - answer: { - sdp: self.peerConn.localDescription.sdp, - type: self.peerConn.localDescription.type - } - }; - sendEvent(self, 'm.call.answer', content); - self.updateState('connecting'); - }, function() { - debuglog("Error setting local description!"); - }, constraints); - }); - - this.updateState('create_answer'); -} - -/** - * Internal - * @private - * @param {Object} stream - */ -MatrixCall.prototype._gotUserMediaForAnswer = function(stream) { - debuglog("_gotUserMediaForAnswer"); - - var self = this; - if (self.state == 'ended') { - return; - } - var localVidEl = self.getLocalVideoElement(); - - if (localVidEl && self.type == 'video') { - localVidEl.autoplay = true; - localVidEl.src = self.URL.createObjectURL(stream); - localVidEl.muted = self; - setTimeout(function() { - var vel = self.getLocalVideoElement(); - if (vel.play) { - vel.play(); - } - }, 0); - } - - self.localAVStream = stream; - - // mute until the connection is established - var audioTracks = stream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = false; - } - self.peerConn.addStream(stream); - - self._create_answer(); -}; - -/** - * Internal - * @private - * @param {Object} event - */ -MatrixCall.prototype._gotLocalIceCandidate = function(event) { - if (event.candidate) { - debuglog( - "Got local ICE " + event.candidate.sdpMid + " candidate: " + - event.candidate.candidate - ); - // As with the offer, note we need to make a copy of this object, not - // pass the original: that broke in Chrome ~m43. - var c = { - candidate: event.candidate.candidate, - sdpMid: event.candidate.sdpMid, - sdpMLineIndex: event.candidate.sdpMLineIndex - }; - - var cands = []; - cands.push(c); - - var content = { - version: 0, - call_id: this.callId, - candidates: cands - }; - - debuglog("Attempting to send the candidate"); - sendEvent(self, 'm.call.candidates', content); - } -}; - -/** - * Used by MatrixClient. - * @protected - * @param {Object} cand - */ -MatrixCall.prototype._gotRemoteIceCandidate = function(cand) { - if (this.state == 'ended') { - return; - } - - debuglog("Got remote ICE " + cand.sdpMid + " candidate: " + cand.candidate); - this.peerConn.addIceCandidate( - new this.webRtc.RtcIceCandidate(cand), - function() {}, - function(e) {} - ); -}; - -/** - * Used by MatrixClient. - * @protected - * @param {Object} msg - */ -MatrixCall.prototype._receivedAnswer = function(msg) { - debuglog("_receivedAnswer"); - - if (this.state == 'ended') { - return; - } - - var self = this; - this.peerConn.setRemoteDescription( - new this.webRtc.RtcSessionDescription(msg.answer), - hookCallback(self, self._onSetRemoteDescriptionSuccess), - hookCallback(self, self._onSetRemoteDescriptionError) - ); - - this.updateState('connecting'); -}; - -/** - * Internal - * @private - * @param {Object} description - */ -MatrixCall.prototype._gotLocalOffer = function(description) { - var self = this; - debuglog("Created offer: " + description); - - if (self.state == 'ended') { - debuglog("Ignoring newly created offer on call ID " + self.callId + - " because the call has ended"); - return; - } - - self.peerConn.setLocalDescription(description, function() { - var content = { - version: 0, - call_id: self.callId, - // OpenWebRTC appears to add extra stuff (like the DTLS fingerprint) - // to the description when setting it on the peerconnection. - // According to the spec it should only add ICE - // candidates. Any ICE candidates that have already been generated - // at this point will probably be sent both in the offer and separately. - // Also, note that we have to make a new object here, copying the - // type and sdp properties. - // Passing the RTCSessionDescription object as-is doesn't work in - // Chrome (as of about m43). - offer: { - sdp: self.peerConn.localDescription.sdp, - type: self.peerConn.localDescription.type - }, - lifetime: MatrixCall.CALL_TIMEOUT_MS - }; - - sendEvent(self, 'm.call.invite', content); - self.updateState('invite_sent'); - }, function() { - debuglog("Error setting local description!"); - }); -}; - -/** - * Internal - * @private - * @param {Object} error - */ -MatrixCall.prototype._getLocalOfferFailed = function(error) { - AndroidCallError("user_media_failed"); - this.hangup("user_media_failed"); -}; - -/** - * Internal - * @private - */ -MatrixCall.prototype._getUserMediaFailed = function() { - AndroidCallError("user_media_failed"); - this.hangup("user_media_failed"); -}; - -/** - * Internal - * @private - */ -MatrixCall.prototype._onIceConnectionStateChanged = function() { - if (this.state == 'ended') { - return; // because ICE can still complete as we're ending the call - } - debuglog( - "Ice connection state changed to: " + this.peerConn.iceConnectionState - ); - - // ideally we'd consider the call to be connected when we get media but - // chrome doesn't implement any of the 'onstarted' events yet - if (this.peerConn.iceConnectionState == 'completed' || - this.peerConn.iceConnectionState == 'connected') { - this.updateState('connected'); - this.didConnect = true; - } else if (this.peerConn.iceConnectionState == 'failed') { - AndroidCallError("ice_failed"); - this.hangup('ice_failed'); - } -}; - -/** - * Internal - * @private - */ -MatrixCall.prototype._onSignallingStateChanged = function() { - debuglog( - "call " + this.callId + ": Signalling state changed to: " + - this.peerConn.signalingState - ); -}; - -/** - * Internal - * @private - */ -MatrixCall.prototype._onSetRemoteDescriptionSuccess = function() { - debuglog("Set remote description"); -}; - -/** - * Internal - * @private - * @param {Object} e - */ -MatrixCall.prototype._onSetRemoteDescriptionError = function(e) { - debuglog("Failed to set remote description" + e); -}; - -/** - * Internal - * @private - * @param {Object} event - */ -MatrixCall.prototype._onAddStream = function(event) { - debuglog("Stream added" + event); - - var s = event.stream; - - this.remoteAVStream = s; - - if (this.direction == 'inbound') { - if (s.getVideoTracks().length > 0) { - this.type = 'video'; - } else { - this.type = 'voice'; - } - } - - var self = this; - forAllTracksOnStream(s, function(t) { - // not currently implemented in chrome - t.onstarted = hookCallback(self, self._onRemoteStreamTrackStarted); - }); - - event.stream.onended = hookCallback(self, self._onRemoteStreamEnded); - // not currently implemented in chrome - event.stream.onstarted = hookCallback(self, self._onRemoteStreamStarted); - - // disable the sound until the connection is established - var audioTracks = this.remoteAVStream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = false; - } - - _tryPlayRemoteStream(this); -}; - -/** - * Internal - * @private - * @param {Object} event - */ -MatrixCall.prototype._onRemoteStreamStarted = function(event) { - if (self.getRemoteVideoElement()) { - self.getRemoteVideoElement().style.display = 'block'; - } - this.updateState('connected'); -}; - -/** - * Internal - * @private - * @param {Object} event - */ -MatrixCall.prototype._onRemoteStreamEnded = function(event) { - debuglog("Remote stream ended"); - - this.updateState('ended'); - this.hangupParty = 'remote'; - stopAllMedia(this); - if (this.peerConn.signalingState != 'closed') { - this.peerConn.close(); - } -}; - -/** - * Internal - * @private - * @param {Object} event - */ -MatrixCall.prototype._onRemoteStreamTrackStarted = function(event) { - this.updateState('connected'); -}; - -/** - * Used by MatrixClient. - * @protected - * @param {Object} msg - */ -MatrixCall.prototype._onHangupReceived = function(msg) { - debuglog("Hangup received"); - terminate(this, "remote", msg.reason, true); -}; - -/** - * Used by MatrixClient. - * @protected - * @param {Object} msg - */ -MatrixCall.prototype._onAnsweredElsewhere = function() { - debuglog("Answered elsewhere"); - terminate(this, "remote", "answered_elsewhere", true); -}; - -/** - * Internal - * @param {MatrixCall} self - * @param {string} eventType - * @param {Object} content - */ -var sendEvent = function(self, eventType, content) { - AndroidSendEvent(self.roomId, eventType, JSON.stringify(content)); -}; - -var terminate = function(self, hangupParty, hangupReason, shouldEmit) { - if (self.getRemoteVideoElement() && self.getRemoteVideoElement().pause) { - self.getRemoteVideoElement().pause(); - } - - if (self.getLocalVideoElement() && self.getLocalVideoElement().pause) { - self.getLocalVideoElement().pause(); - } - - self.updateState('ended'); - self.hangupParty = hangupParty; - self.hangupReason = hangupReason; - stopAllMedia(self); - - if (self.peerConn && self.peerConn.signalingState !== 'closed') { - self.peerConn.close(); - } -}; - -var stopAllMedia = function(self) { - if (self.localAVStream) { - forAllTracksOnStream(self.localAVStream, function(t) { - if (t.stop) { - t.stop(); - } - }); - // also call stop on the main stream so firefox will stop sharing - // the mic - if (self.localAVStream.stop) { - self.localAVStream.stop(); - } - } - if (self.remoteAVStream) { - forAllTracksOnStream(self.remoteAVStream, function(t) { - if (t.stop) { - t.stop(); - } - }); - } -}; - -var _tryPlayRemoteStream = function(self) { - // play the remote stream only when the connection is established - // else it triggers buzzer sound while establishing the connection. - if (self.getRemoteVideoElement() && self.remoteAVStream && (self.state == 'connected')) { - var player = self.getRemoteVideoElement(); - player.autoplay = true; - player.src = self.URL.createObjectURL(self.remoteAVStream); - setTimeout(function() { - var vel = self.getRemoteVideoElement(); - if (vel.play) { - vel.play(); - } - }, 0); - } -}; - - -var debuglog = function() { - if (0 < arguments.length) { - androidLog(arguments[0]); - } - if (DEBUG) { - console.log.apply(console, arguments); - } -}; - -var _placeCallWithConstraints = function(self, constraints) { - //self.client.callList[self.callId] = self; - self.webRtc.getUserMedia( - constraints, - hookCallback(self, self._gotUserMediaForInvite), - hookCallback(self, self._getUserMediaFailed) - ); - self.updateState('wait_local_media'); - self.direction = 'outbound'; - self.config = constraints; -}; - -var _createPeerConnection = function(self) { - var servers = self.turnServers; - - var iceServers = []; - - if (servers && servers.uris) { - iceServers.push({ - 'urls': servers.uris, - 'username': servers.username, - 'credential': servers.password, - }); - } else { - iceServers = servers; - } - - var pc = new self.webRtc.RtcPeerConnection({ - iceServers: iceServers - }); - pc.oniceconnectionstatechange = hookCallback(self, self._onIceConnectionStateChanged); - pc.onsignalingstatechange = hookCallback(self, self._onSignallingStateChanged); - pc.onicecandidate = hookCallback(self, self._gotLocalIceCandidate); - pc.onaddstream = hookCallback(self, self._onAddStream); - return pc; -}; - -var _getUserMediaVideoContraints = function(callType) { - switch (callType) { - case 'voice': - return ({audio: true, video: false}); - case 'video': - return ({audio: true, video: { - mandatory: { - minWidth: 640, - //maxWidth: 640, - // the local preview is cropped when the height is defined - //minHeight: 360, - //maxHeight: 360, - } - }}); - } -}; - -var hookCallback = function(call, fn) { - return function() { - return fn.apply(call, arguments); - }; -}; - -var forAllVideoTracksOnStream = function(s, f) { - var tracks = s.getVideoTracks(); - for (var i = 0; i < tracks.length; i++) { - f(tracks[i]); - } -}; - -var forAllAudioTracksOnStream = function(s, f) { - var tracks = s.getAudioTracks(); - for (var i = 0; i < tracks.length; i++) { - f(tracks[i]); - } -}; - -var forAllTracksOnStream = function(s, f) { - forAllVideoTracksOnStream(s, f); - forAllAudioTracksOnStream(s, f); -}; - - -/** - * Create a new Matrix call for the browser. - * @param {MatrixClient} client The client instance to use. - * @param {string} roomId The room the call is in. - * @return {MatrixCall} the call or null if the browser doesn't support calling. - */ -function createNewMatrixCall(/*client,*/ roomId) { - - androidLog("createNewMatrixCall"); - - var w = window; - var doc = document; - if (!w || !doc) { - return null; - } - var webRtc = {}; - webRtc.isOpenWebRTC = function() { - var scripts = doc.getElementById("script"); - if (!scripts || !scripts.length) { - return false; - } - for (var i = 0; i < scripts.length; i++) { - if (scripts[i].src.indexOf("owr.js") > -1) { - return true; - } - } - return false; - }; - - var getUserMedia = ( - w.navigator.getUserMedia || w.navigator.webkitGetUserMedia || - w.navigator.mozGetUserMedia - ); - if (getUserMedia) { - webRtc.getUserMedia = function() { - return getUserMedia.apply(w.navigator, arguments); - }; - } - webRtc.RtcPeerConnection = ( - w.RTCPeerConnection || w.webkitRTCPeerConnection || w.mozRTCPeerConnection - ); - webRtc.RtcSessionDescription = ( - w.RTCSessionDescription || w.webkitRTCSessionDescription || - w.mozRTCSessionDescription - ); - webRtc.RtcIceCandidate = ( - w.RTCIceCandidate || w.webkitRTCIceCandidate || w.mozRTCIceCandidate - ); - - webRtc.vendor = null; - if (w.mozRTCPeerConnection) { - webRtc.vendor = "mozilla"; - } - else if (w.webkitRTCPeerConnection) { - webRtc.vendor = "webkit"; - } - else if (w.RTCPeerConnection) { - webRtc.vendor = "generic"; - } - if (!webRtc.RtcIceCandidate || !webRtc.RtcSessionDescription || - !webRtc.RtcPeerConnection || !webRtc.getUserMedia) { - return null; // Web RTC is not supported. - } - - var turnServersString = AndroidGetTurnServer(); - var turnServs = null; - - if (turnServersString) { - turnServs = JSON.parse(turnServersString); - } - - var opts = { - webRtc: webRtc, - turnServers:turnServs, - URL: w.URL, - roomId: roomId - }; - - return new MatrixCall(opts); -}; diff --git a/matrix-sdk/src/main/assets/www/utils.js b/matrix-sdk/src/main/assets/www/utils.js deleted file mode 100755 index f7b1547b8..000000000 --- a/matrix-sdk/src/main/assets/www/utils.js +++ /dev/null @@ -1,318 +0,0 @@ -"use strict"; -/** - * This is an internal module. - * @module utils - */ - -/** - * Encode a dictionary of query parameters. - * @param {Object} params A dict of key/values to encode e.g. - * {"foo": "bar", "baz": "taz"} - * @return {string} The encoded string e.g. foo=bar&baz=taz - */ -module.exports.encodeParams = function(params) { - var qs = ""; - for (var key in params) { - if (!params.hasOwnProperty(key)) { continue; } - qs += "&" + encodeURIComponent(key) + "=" + - encodeURIComponent(params[key]); - } - return qs.substring(1); -}; - -/** - * Encodes a URI according to a set of template variables. Variables will be - * passed through encodeURIComponent. - * @param {string} pathTemplate The path with template variables e.g. '/foo/$bar'. - * @param {Object} variables The key/value pairs to replace the template - * variables with. E.g. { "$bar": "baz" }. - * @return {string} The result of replacing all template variables e.g. '/foo/baz'. - */ -module.exports.encodeUri = function(pathTemplate, variables) { - for (var key in variables) { - if (!variables.hasOwnProperty(key)) { continue; } - pathTemplate = pathTemplate.replace( - key, encodeURIComponent(variables[key]) - ); - } - return pathTemplate; -}; - -/** - * Applies a map function to the given array. - * @param {Array} array The array to apply the function to. - * @param {Function} fn The function that will be invoked for each element in - * the array with the signature fn(element){...} - * @return {Array} A new array with the results of the function. - */ -module.exports.map = function(array, fn) { - var results = new Array(array.length); - for (var i = 0; i < array.length; i++) { - results[i] = fn(array[i]); - } - return results; -}; - -/** - * Applies a filter function to the given array. - * @param {Array} array The array to apply the function to. - * @param {Function} fn The function that will be invoked for each element in - * the array. It should return true to keep the element. The function signature - * looks like fn(element, index, array){...}. - * @return {Array} A new array with the results of the function. - */ -module.exports.filter = function(array, fn) { - var results = []; - for (var i = 0; i < array.length; i++) { - if (fn(array[i], i, array)) { - results.push(array[i]); - } - } - return results; -}; - -/** - * Get the keys for an object. Same as Object.keys(). - * @param {Object} obj The object to get the keys for. - * @return {string[]} The keys of the object. - */ -module.exports.keys = function(obj) { - var keys = []; - for (var key in obj) { - if (!obj.hasOwnProperty(key)) { continue; } - keys.push(key); - } - return keys; -}; - -/** - * Get the values for an object. - * @param {Object} obj The object to get the values for. - * @return {Array<*>} The values of the object. - */ -module.exports.values = function(obj) { - var values = []; - for (var key in obj) { - if (!obj.hasOwnProperty(key)) { continue; } - values.push(obj[key]); - } - return values; -}; - -/** - * Invoke a function for each item in the array. - * @param {Array} array The array. - * @param {Function} fn The function to invoke for each element. Has the - * function signature fn(element, index). - */ -module.exports.forEach = function(array, fn) { - for (var i = 0; i < array.length; i++) { - fn(array[i], i); - } -}; - -/** - * The findElement() method returns a value in the array, if an element in the array - * satisfies (returns true) the provided testing function. Otherwise undefined - * is returned. - * @param {Array} array The array. - * @param {Function} fn Function to execute on each value in the array, with the - * function signature fn(element, index, array) - * @param {boolean} reverse True to search in reverse order. - * @return {*} The first value in the array which returns true for - * the given function. - */ -module.exports.findElement = function(array, fn, reverse) { - var i; - if (reverse) { - for (i = array.length - 1; i >= 0; i--) { - if (fn(array[i], i, array)) { - return array[i]; - } - } - } - else { - for (i = 0; i < array.length; i++) { - if (fn(array[i], i, array)) { - return array[i]; - } - } - } -}; - -/** - * The removeElement() method removes the first element in the array that - * satisfies (returns true) the provided testing function. - * @param {Array} array The array. - * @param {Function} fn Function to execute on each value in the array, with the - * function signature fn(element, index, array). Return true to - * remove this element and break. - * @param {boolean} reverse True to search in reverse order. - */ -module.exports.removeElement = function(array, fn, reverse) { - var i; - if (reverse) { - for (i = array.length - 1; i >= 0; i--) { - if (fn(array[i], i, array)) { - array.splice(i, 1); - return; } - } - } - else { - for (i = 0; i < array.length; i++) { - if (fn(array[i], i, array)) { - array.splice(i, 1); - return; - } - } - } -}; - -/** - * Checks if the given thing is a function. - * @param {*} value The thing to check. - * @return {boolean} True if it is a function. - */ -module.exports.isFunction = function(value) { - return Object.prototype.toString.call(value) == "[object Function]"; -}; - -/** - * Checks if the given thing is an array. - * @param {*} value The thing to check. - * @return {boolean} True if it is an array. - */ -module.exports.isArray = function(value) { - return Boolean(value && value.constructor === Array); -}; - -/** - * Checks that the given object has the specified keys. - * @param {Object} obj The object to check. - * @param {string[]} keys The list of keys that 'obj' must have. - * @throws If the object is missing keys. - */ -module.exports.checkObjectHasKeys = function(obj, keys) { - for (var i = 0; i < keys.length; i++) { - if (!obj.hasOwnProperty(keys[i])) { - throw new Error("Missing required key: " + keys[i]); - } - } -}; - -/** - * Checks that the given object has no extra keys other than the specified ones. - * @param {Object} obj The object to check. - * @param {string[]} allowedKeys The list of allowed key names. - * @throws If there are extra keys. - */ -module.exports.checkObjectHasNoAdditionalKeys = function(obj, allowedKeys) { - for (var key in obj) { - if (!obj.hasOwnProperty(key)) { continue; } - if (allowedKeys.indexOf(key) === -1) { - throw new Error("Unknown key: " + key); - } - } -}; - -/** - * Deep copy the given object. The object MUST NOT have circular references and - * MUST NOT have functions. - * @param {Object} obj The object to deep copy. - * @return {Object} A copy of the object without any references to the original. - */ -module.exports.deepCopy = function(obj) { - return JSON.parse(JSON.stringify(obj)); -}; - -/** - * Inherit the prototype methods from one constructor into another. This is a - * port of the Node.js implementation with an Object.create polyfill. - * - * @param {function} ctor Constructor function which needs to inherit the - * prototype. - * @param {function} superCtor Constructor function to inherit prototype from. - */ -module.exports.inherits = function(ctor, superCtor) { - // Add Object.create polyfill for IE8 - // Source: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript - // /Reference/Global_Objects/Object/create#Polyfill - if (typeof Object.create != 'function') { - // Production steps of ECMA-262, Edition 5, 15.2.3.5 - // Reference: http://es5.github.io/#x15.2.3.5 - Object.create = (function() { - // To save on memory, use a shared constructor - function Temp() {} - - // make a safe reference to Object.prototype.hasOwnProperty - var hasOwn = Object.prototype.hasOwnProperty; - - return function(O) { - // 1. If Type(O) is not Object or Null throw a TypeError exception. - if (typeof O != 'object') { - throw new TypeError('Object prototype may only be an Object or null'); - } - - // 2. Let obj be the result of creating a new object as if by the - // expression new Object() where Object is the standard built-in - // constructor with that name - // 3. Set the [[Prototype]] internal property of obj to O. - Temp.prototype = O; - var obj = new Temp(); - Temp.prototype = null; // Let's not keep a stray reference to O... - - // 4. If the argument Properties is present and not undefined, add - // own properties to obj as if by calling the standard built-in - // function Object.defineProperties with arguments obj and - // Properties. - if (arguments.length > 1) { - // Object.defineProperties does ToObject on its first argument. - var Properties = Object(arguments[1]); - for (var prop in Properties) { - if (hasOwn.call(Properties, prop)) { - obj[prop] = Properties[prop]; - } - } - } - - // 5. Return obj - return obj; - }; - })(); - } - // END polyfill - - // Add util.inherits from Node.js - // Source: - // https://github.com/joyent/node/blob/master/lib/util.js - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); -}; diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/MXSession.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/MXSession.java index 41a96ec10..5e17b4ae4 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/MXSession.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/MXSession.java @@ -38,7 +38,6 @@ import org.matrix.androidsdk.core.FilterUtil; import org.matrix.androidsdk.core.JsonUtils; import org.matrix.androidsdk.core.Log; -import org.matrix.androidsdk.core.PolymorphicRequestBodyConverter; import org.matrix.androidsdk.core.UnsentEventsManager; import org.matrix.androidsdk.core.VersionsUtilKt; import org.matrix.androidsdk.core.callback.ApiCallback; @@ -375,7 +374,7 @@ public void onReadReceiptsLoaded(final String roomId) { // return the default cache manager mLatestChatMessageCache = new MXLatestChatMessageCache(mCredentials.userId); - mMediaCache = new MXMediaCache(mContentManager, mNetworkConnectivityReceiver, mCredentials.userId, appContext); + mMediaCache = new MXMediaCache(hsConfig, mContentManager, mNetworkConnectivityReceiver, mCredentials.userId, appContext); mDataHandler.setMediaCache(mMediaCache); mMediaScanRestClient.setMxStore(mDataHandler.getStore()); diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/CallSoundsManager.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/CallSoundsManager.java index 95efb7856..669148eec 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/CallSoundsManager.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/CallSoundsManager.java @@ -166,50 +166,47 @@ private void dispatchAudioConfigurationUpdate() { // audio focus management private final Set mAudioFocusListeners = new HashSet<>(); - private final AudioManager.OnAudioFocusChangeListener mFocusListener = new AudioManager.OnAudioFocusChangeListener() { - @Override - public void onAudioFocusChange(int aFocusEvent) { - switch (aFocusEvent) { - case AudioManager.AUDIOFOCUS_GAIN: - Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_GAIN"); - // TODO resume voip call (ex: ending GSM call) - break; - - case AudioManager.AUDIOFOCUS_LOSS: - Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_LOSS"); - // TODO pause voip call (ex: incoming GSM call) - break; - - case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: - Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_GAIN_TRANSIENT"); - break; - - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_LOSS_TRANSIENT"); - // TODO pause voip call (ex: incoming GSM call) - break; - - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - // TODO : continue playing at an attenuated level - Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"); - break; - - case AudioManager.AUDIOFOCUS_REQUEST_FAILED: - Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_REQUEST_FAILED"); - break; - - default: - break; - } + private final AudioManager.OnAudioFocusChangeListener mFocusListener = aFocusEvent -> { + switch (aFocusEvent) { + case AudioManager.AUDIOFOCUS_GAIN: + Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_GAIN"); + // TODO resume voip call (ex: ending GSM call) + break; + + case AudioManager.AUDIOFOCUS_LOSS: + Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_LOSS"); + // TODO pause voip call (ex: incoming GSM call) + break; + + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: + Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_GAIN_TRANSIENT"); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_LOSS_TRANSIENT"); + // TODO pause voip call (ex: incoming GSM call) + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // TODO : continue playing at an attenuated level + Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"); + break; + + case AudioManager.AUDIOFOCUS_REQUEST_FAILED: + Log.d(LOG_TAG, "## OnAudioFocusChangeListener(): AUDIOFOCUS_REQUEST_FAILED"); + break; + + default: + break; + } - synchronized (LOG_TAG) { - // notify listeners - for (OnAudioFocusListener listener : mAudioFocusListeners) { - try { - listener.onFocusChanged(aFocusEvent); - } catch (Exception e) { - Log.e(LOG_TAG, "## onFocusChanged() failed " + e.getMessage(), e); - } + synchronized (LOG_TAG) { + // notify listeners + for (OnAudioFocusListener listener : mAudioFocusListeners) { + try { + listener.onFocusChanged(aFocusEvent); + } catch (Exception e) { + Log.e(LOG_TAG, "## onFocusChanged() failed " + e.getMessage(), e); } } } @@ -496,18 +493,15 @@ public void startSound(int resId, boolean isLooping, final OnMediaListener liste listener.onMediaPlay(); } - mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - if (null != listener) { - listener.onMediaCompleted(); - } - mPlayingSound = -1; + mMediaPlayer.setOnCompletionListener(mp -> { + if (null != listener) { + listener.onMediaCompleted(); + } + mPlayingSound = -1; - if (null != mMediaPlayer) { - mMediaPlayer.release(); - mMediaPlayer = null; - } + if (null != mMediaPlayer) { + mMediaPlayer.release(); + mMediaPlayer = null; } }); @@ -540,7 +534,7 @@ private static Uri getRingToneUri(Context context, int resId, String filename) { File ringFile = new File(ringToneUri.toString()); // check if the file exists - if ((null != ringFile) && ringFile.exists() && ringFile.canRead()) { + if (ringFile.exists() && ringFile.canRead()) { // provide it return ringToneUri; } diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/HeadsetConnectionReceiver.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/HeadsetConnectionReceiver.java index 65561c43a..82ca3e097 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/HeadsetConnectionReceiver.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/HeadsetConnectionReceiver.java @@ -173,7 +173,13 @@ public void onReceive(final Context aContext, final Intent aIntent) { } isBTHeadsetUpdate = false; } else { - int state = BluetoothAdapter.getDefaultAdapter().getProfileConnectionState(BluetoothProfile.HEADSET); + int state; + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter == null) { + state = BluetoothAdapter.STATE_DISCONNECTED; + } else { + state = bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); + } Log.d(LOG_TAG, "bluetooth headset state " + state); newState = (BluetoothAdapter.STATE_CONNECTED == state); @@ -184,14 +190,11 @@ public void onReceive(final Context aContext, final Intent aIntent) { mIsHeadsetPlugged = newState; // wait a little else route to BT headset does not work. - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - if (isBTHeadsetUpdate) { - onBluetoothHeadsetUpdate(mIsHeadsetPlugged); - } else { - onWiredHeadsetUpdate(mIsHeadsetPlugged); - } + new Handler(Looper.getMainLooper()).postDelayed(() -> { + if (isBTHeadsetUpdate) { + onBluetoothHeadsetUpdate(mIsHeadsetPlugged); + } else { + onWiredHeadsetUpdate(mIsHeadsetPlugged); } }, 1000); } @@ -220,6 +223,7 @@ private static AudioManager getAudioManager(Context context) { public static boolean isHeadsetPlugged(Context context) { if (null == mIsHeadsetPlugged) { AudioManager audioManager = getAudioManager(context); + //noinspection deprecation mIsHeadsetPlugged = isBTHeadsetPlugged() || audioManager.isWiredHeadsetOn(); } @@ -230,6 +234,11 @@ public static boolean isHeadsetPlugged(Context context) { * @return true if bluetooth headset is plugged */ public static boolean isBTHeadsetPlugged() { - return (BluetoothAdapter.STATE_CONNECTED == BluetoothAdapter.getDefaultAdapter().getProfileConnectionState(BluetoothProfile.HEADSET)); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter == null) { + return false; + } else { + return (BluetoothAdapter.STATE_CONNECTED == bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)); + } } } diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/IMXCall.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/IMXCall.java index e23ba6151..8be57aebb 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/IMXCall.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/IMXCall.java @@ -31,7 +31,7 @@ /** * Audio/video call interface. - * See {@link MXWebRtcCall} and {@link MXChromeCall}. + * See {@link MXWebRtcCall}. */ public interface IMXCall { diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCall.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCall.java index a27cc0073..ee6f76189 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCall.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCall.java @@ -108,12 +108,12 @@ public class MXCall implements IMXCall { private boolean mIsConference = false; /** - * List of events to sends to mCallSignalingRoom + * List of events to send to mCallSignalingRoom */ protected final List mPendingEvents = new ArrayList<>(); /** - * The sending eevent. + * The sending event. */ private Event mPendingEvent; @@ -156,11 +156,11 @@ public List getIceServers() { for (int index = 0; index < uris.size(); index++) { String url = uris.get(index).getAsString(); + PeerConnection.IceServer.Builder iceServerBuilder = PeerConnection.IceServer.builder(url); if ((null != username) && (null != password)) { - iceServers.add(new PeerConnection.IceServer(url, username, password)); - } else { - iceServers.add(new PeerConnection.IceServer(url)); + iceServerBuilder.setUsername(username).setPassword(password); } + iceServers.add(iceServerBuilder.createIceServer()); } } catch (Exception e) { Log.e(LOG_TAG, "## createLocalStream(): Exception in ICE servers list Msg=" + e.getMessage(), e); @@ -539,7 +539,7 @@ protected void dispatchOnCallError(String error) { * @param newState the new state */ protected void dispatchOnStateDidChange(String newState) { - Log.d(LOG_TAG, "## dispatchOnCallErrorOnStateDidChange(): " + newState); + Log.d(LOG_TAG, "## dispatchOnStateDidChange(): " + newState); // set the call start time if (TextUtils.equals(CALL_STATE_CONNECTED, newState) && (-1 == mStartTime)) { @@ -602,67 +602,58 @@ protected void dispatchOnCallEnd(int aEndCallReasonId) { * Send the next pending events */ protected void sendNextEvent() { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - // do not send any new message - if (isCallEnded() && (null != mPendingEvents)) { - mPendingEvents.clear(); - } + mUIThreadHandler.post(() -> { + // do not send any new message + if (isCallEnded()) { + mPendingEvents.clear(); + } - // ready to send - if ((null == mPendingEvent) && (0 != mPendingEvents.size())) { - mPendingEvent = mPendingEvents.get(0); - mPendingEvents.remove(mPendingEvent); - - Log.d(LOG_TAG, "## sendNextEvent() : sending event of type " + mPendingEvent.getType() + " event id " + mPendingEvent.eventId); - mCallSignalingRoom.sendEvent(mPendingEvent, new ApiCallback() { - @Override - public void onSuccess(Void info) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - Log.d(LOG_TAG, "## sendNextEvent() : event " + mPendingEvent.eventId + " is sent"); - - mPendingEvent = null; - sendNextEvent(); - } - }); - } + // ready to send + if ((null == mPendingEvent) && (0 != mPendingEvents.size())) { + mPendingEvent = mPendingEvents.get(0); + mPendingEvents.remove(mPendingEvent); + + Log.d(LOG_TAG, "## sendNextEvent() : sending event of type " + mPendingEvent.getType() + " event id " + mPendingEvent.eventId); + mCallSignalingRoom.sendEvent(mPendingEvent, new ApiCallback() { + @Override + public void onSuccess(Void info) { + mUIThreadHandler.post(() -> { + Log.d(LOG_TAG, "## sendNextEvent() : event " + mPendingEvent.eventId + " is sent"); + + mPendingEvent = null; + sendNextEvent(); + }); + } - private void commonFailure(String reason) { - Log.d(LOG_TAG, "## sendNextEvent() : event " + mPendingEvent.eventId + " failed to be sent " + reason); - - // let try next candidate event - if (TextUtils.equals(mPendingEvent.getType(), Event.EVENT_TYPE_CALL_CANDIDATES)) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - mPendingEvent = null; - sendNextEvent(); - } - }); - } else { - hangup(reason); - } - } + private void commonFailure(String reason) { + Log.d(LOG_TAG, "## sendNextEvent() : event " + mPendingEvent.eventId + " failed to be sent " + reason); - @Override - public void onNetworkError(Exception e) { - commonFailure(e.getLocalizedMessage()); + // let try next candidate event + if (TextUtils.equals(mPendingEvent.getType(), Event.EVENT_TYPE_CALL_CANDIDATES)) { + mUIThreadHandler.post(() -> { + mPendingEvent = null; + sendNextEvent(); + }); + } else { + hangup(reason); } + } - @Override - public void onMatrixError(MatrixError e) { - commonFailure(e.getLocalizedMessage()); - } + @Override + public void onNetworkError(Exception e) { + commonFailure(e.getLocalizedMessage()); + } - @Override - public void onUnexpectedError(Exception e) { - commonFailure(e.getLocalizedMessage()); - } - }); - } + @Override + public void onMatrixError(MatrixError e) { + commonFailure(e.getLocalizedMessage()); + } + + @Override + public void onUnexpectedError(Exception e) { + commonFailure(e.getLocalizedMessage()); + } + }); } }); } @@ -705,12 +696,7 @@ protected void sendHangup(String reason) { Event event = new Event(Event.EVENT_TYPE_CALL_HANGUP, hangupContent, mSession.getCredentials().userId, mCallSignalingRoom.getRoomId()); // local notification to indicate the end of call - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - dispatchOnCallEnd(END_CALL_REASON_USER_HIMSELF); - } - }); + mUIThreadHandler.post(() -> dispatchOnCallEnd(END_CALL_REASON_USER_HIMSELF)); Log.d(LOG_TAG, "## sendHangup(): reason=" + reason); diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCallsManager.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCallsManager.java index acd5ffad9..855849892 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCallsManager.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXCallsManager.java @@ -61,26 +61,14 @@ public class MXCallsManager { private static final String LOG_TAG = MXCallsManager.class.getSimpleName(); - /** - * Defines the call classes. - */ - public enum CallClass { - // disabled because of https://github.com/vector-im/riot-android/issues/1660 - //CHROME_CLASS, - WEBRTC_CLASS, - DEFAULT_CLASS - } - - private MXSession mSession = null; - private Context mContext = null; + private MXSession mSession; + private Context mContext; - private CallRestClient mCallResClient = null; + private CallRestClient mCallResClient; private JsonElement mTurnServer = null; private Timer mTurnServerTimer = null; private boolean mSuspendTurnServerRefresh = false; - private CallClass mPreferredCallClass = CallClass.WEBRTC_CLASS; - // active calls private final Map mCallsByCallId = new HashMap<>(); @@ -95,7 +83,7 @@ public enum CallClass { public static String defaultStunServerUri; - /** + /* * To create an outgoing call * 1- CallsManager.createCallInRoom() * 2- on success, IMXCall.createCallView @@ -163,49 +151,10 @@ public void onLiveEvent(Event event, RoomState roomState) { /** * @return true if the call feature is supported + * @apiNote Performs an implicit initialization of the PeerConnectionFactory */ public boolean isSupported() { - return /*MXChromeCall.isSupported() || */ MXWebRtcCall.isSupported(mContext); - } - - /** - * @return the list of supported classes - */ - public Collection supportedClass() { - List list = new ArrayList<>(); - - /*if (MXChromeCall.isSupported()) { - list.add(CallClass.CHROME_CLASS); - }*/ - - if (MXWebRtcCall.isSupported(mContext)) { - list.add(CallClass.WEBRTC_CLASS); - } - - Log.d(LOG_TAG, "supportedClass " + list); - - return list; - } - - /** - * @param callClass set the default callClass - */ - public void setDefaultCallClass(CallClass callClass) { - Log.d(LOG_TAG, "setDefaultCallClass " + callClass); - - boolean isUpdatable = false; - - /*if (callClass == CallClass.CHROME_CLASS) { - isUpdatable = MXChromeCall.isSupported(); - }*/ - - if (callClass == CallClass.WEBRTC_CLASS) { - isUpdatable = MXWebRtcCall.isSupported(mContext); - } - - if (isUpdatable) { - mPreferredCallClass = callClass; - } + return MXWebRtcCall.isSupported(mContext); } /** @@ -219,24 +168,15 @@ private IMXCall createCall(String callId) { IMXCall call = null; - // default - /*if (((CallClass.CHROME_CLASS == mPreferredCallClass) || (CallClass.DEFAULT_CLASS == mPreferredCallClass)) && MXChromeCall.isSupported()) { - call = new MXChromeCall(mSession, mContext, getTurnServer()); - }*/ - - // webrtc -// if (null == call) { try { call = new MXWebRtcCall(mSession, mContext, getTurnServer(), defaultStunServerUri); + // a valid callid is provided + if (null != callId) { + call.setCallId(callId); + } } catch (Exception e) { Log.e(LOG_TAG, "createCall " + e.getMessage(), e); } -// } - - // a valid callid is provided - if (null != callId) { - call.setCallId(callId); - } return call; } @@ -358,7 +298,7 @@ public boolean hasActiveCalls() { for (String callId : callIds) { IMXCall call = mCallsByCallId.get(callId); - if (TextUtils.equals(call.getCallState(), IMXCall.CALL_STATE_ENDED)) { + if (null != call && TextUtils.equals(call.getCallState(), IMXCall.CALL_STATE_ENDED)) { Log.d(LOG_TAG, "# hasActiveCalls() : the call " + callId + " is not anymore valid"); callIdsToRemove.add(callId); } else { @@ -387,7 +327,7 @@ public void handleCallEvent(final IMXStore store, final Event event) { Log.d(LOG_TAG, "handleCallEvent " + event.getType()); // always run the call event in the UI thread - // MXChromeCall does not work properly in other thread (because of the webview) + // TODO: This was introduced because of MXChromeCall, check if it is required for MXWebRtcCall as well mUIThreadHandler.post(new Runnable() { @Override public void run() { @@ -484,15 +424,12 @@ public void run() { } // warn that a call has been hung up - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - // must warn anyway any listener that the call has been killed - // for example, when the device is in locked screen - // the callview is not created but the device is ringing - // if the other participant ends the call, the ring should stop - dispatchOnCallHangUp(call); - } + mUIThreadHandler.post(() -> { + // must warn anyway any listener that the call has been killed + // for example, when the device is in locked screen + // the callview is not created but the device is ringing + // if the other participant ends the call, the ring should stop + dispatchOnCallHangUp(call); }); } } @@ -508,127 +445,125 @@ public void run() { public void checkPendingIncomingCalls() { //Log.d(LOG_TAG, "checkPendingIncomingCalls"); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (mxPendingIncomingCallId.size() > 0) { - for (String callId : mxPendingIncomingCallId) { - final IMXCall call = getCallWithCallId(callId); - - if (null != call) { - final Room room = call.getRoom(); - - // for encrypted rooms with 2 members - // check if there are some unknown devices before warning - // of the incoming call. - // If there are some unknown devices, the answer event would not be encrypted. - if ((null != room) - && room.isEncrypted() - && mSession.getCrypto().warnOnUnknownDevices() - && room.getNumberOfJoinedMembers() == 2) { - - // test if the encrypted events are sent only to the verified devices (any room) - mSession.getCrypto().getGlobalBlacklistUnverifiedDevices(new SimpleApiCallback() { - @Override - public void onSuccess(Boolean sendToVerifiedDevicesOnly) { - if (sendToVerifiedDevicesOnly) { - dispatchOnIncomingCall(call, null); - } else { - // test if the encrypted events are sent only to the verified devices (only this room) - mSession.getCrypto().isRoomBlacklistUnverifiedDevices(room.getRoomId(), new SimpleApiCallback() { - @Override - public void onSuccess(Boolean sendToVerifiedDevicesOnly) { - if (sendToVerifiedDevicesOnly) { - dispatchOnIncomingCall(call, null); - } else { - room.getJoinedMembersAsync(new ApiCallback>() { - - @Override - public void onNetworkError(Exception e) { - dispatchOnIncomingCall(call, null); - } - - @Override - public void onMatrixError(MatrixError e) { - dispatchOnIncomingCall(call, null); - } - - @Override - public void onUnexpectedError(Exception e) { - dispatchOnIncomingCall(call, null); - } - - @Override - public void onSuccess(List members) { - String userId1 = members.get(0).getUserId(); - String userId2 = members.get(1).getUserId(); - - Log.d(LOG_TAG, "## checkPendingIncomingCalls() : check the unknown devices"); - - // - mSession.getCrypto() - .checkUnknownDevices(Arrays.asList(userId1, userId2), new ApiCallback() { - @Override - public void onSuccess(Void anything) { - Log.d(LOG_TAG, "## checkPendingIncomingCalls() : no unknown device"); - dispatchOnIncomingCall(call, null); - } - - @Override - public void onNetworkError(Exception e) { - Log.e(LOG_TAG, - "## checkPendingIncomingCalls() : checkUnknownDevices failed " - + e.getMessage(), e); - dispatchOnIncomingCall(call, null); - } - - @Override - public void onMatrixError(MatrixError e) { - MXUsersDevicesMap unknownDevices = null; - - if (e instanceof MXCryptoError) { - MXCryptoError cryptoError = (MXCryptoError) e; - - if (MXCryptoError.UNKNOWN_DEVICES_CODE.equals(cryptoError.errcode)) { - unknownDevices = - (MXUsersDevicesMap) cryptoError.mExceptionData; - } - } - - if (null != unknownDevices) { - Log.d(LOG_TAG, "## checkPendingIncomingCalls() :" + - " checkUnknownDevices found some unknown devices"); - } else { - Log.e(LOG_TAG, "## checkPendingIncomingCalls() :" + - " checkUnknownDevices failed " + e.getMessage()); + mUIThreadHandler.post(() -> { + if (mxPendingIncomingCallId.size() > 0) { + for (String callId : mxPendingIncomingCallId) { + final IMXCall call = getCallWithCallId(callId); + + if (null != call) { + final Room room = call.getRoom(); + + // for encrypted rooms with 2 members + // check if there are some unknown devices before warning + // of the incoming call. + // If there are some unknown devices, the answer event would not be encrypted. + if ((null != room) + && room.isEncrypted() + && mSession.getCrypto() != null + && mSession.getCrypto().warnOnUnknownDevices() + && room.getNumberOfJoinedMembers() == 2) { + + // test if the encrypted events are sent only to the verified devices (any room) + mSession.getCrypto().getGlobalBlacklistUnverifiedDevices(new SimpleApiCallback() { + @Override + public void onSuccess(Boolean sendToVerifiedDevicesOnly) { + if (sendToVerifiedDevicesOnly) { + dispatchOnIncomingCall(call, null); + } else { + // test if the encrypted events are sent only to the verified devices (only this room) + mSession.getCrypto().isRoomBlacklistUnverifiedDevices(room.getRoomId(), new SimpleApiCallback() { + @Override + public void onSuccess(Boolean sendToVerifiedDevicesOnly) { + if (sendToVerifiedDevicesOnly) { + dispatchOnIncomingCall(call, null); + } else { + room.getJoinedMembersAsync(new ApiCallback>() { + + @Override + public void onNetworkError(Exception e) { + dispatchOnIncomingCall(call, null); + } + + @Override + public void onMatrixError(MatrixError e) { + dispatchOnIncomingCall(call, null); + } + + @Override + public void onUnexpectedError(Exception e) { + dispatchOnIncomingCall(call, null); + } + + @Override + public void onSuccess(List members) { + String userId1 = members.get(0).getUserId(); + String userId2 = members.get(1).getUserId(); + + Log.d(LOG_TAG, "## checkPendingIncomingCalls() : check the unknown devices"); + + // + mSession.getCrypto() + .checkUnknownDevices(Arrays.asList(userId1, userId2), new ApiCallback() { + @Override + public void onSuccess(Void anything) { + Log.d(LOG_TAG, "## checkPendingIncomingCalls() : no unknown device"); + dispatchOnIncomingCall(call, null); + } + + @Override + public void onNetworkError(Exception e) { + Log.e(LOG_TAG, + "## checkPendingIncomingCalls() : checkUnknownDevices failed " + + e.getMessage(), e); + dispatchOnIncomingCall(call, null); + } + + @Override + public void onMatrixError(MatrixError e) { + MXUsersDevicesMap unknownDevices = null; + + if (e instanceof MXCryptoError) { + MXCryptoError cryptoError = (MXCryptoError) e; + + if (MXCryptoError.UNKNOWN_DEVICES_CODE.equals(cryptoError.errcode)) { + unknownDevices = + (MXUsersDevicesMap) cryptoError.mExceptionData; } - - dispatchOnIncomingCall(call, unknownDevices); } - @Override - public void onUnexpectedError(Exception e) { + if (null != unknownDevices) { + Log.d(LOG_TAG, "## checkPendingIncomingCalls() :" + + " checkUnknownDevices found some unknown devices"); + } else { Log.e(LOG_TAG, "## checkPendingIncomingCalls() :" + - " checkUnknownDevices failed " + e.getMessage(), e); - dispatchOnIncomingCall(call, null); + " checkUnknownDevices failed " + e.getMessage()); } - }); - } - }); - } + + dispatchOnIncomingCall(call, unknownDevices); + } + + @Override + public void onUnexpectedError(Exception e) { + Log.e(LOG_TAG, "## checkPendingIncomingCalls() :" + + " checkUnknownDevices failed " + e.getMessage(), e); + dispatchOnIncomingCall(call, null); + } + }); + } + }); } - }); - } + } + }); } - }); - } else { - dispatchOnIncomingCall(call, null); - } + } + }); + } else { + dispatchOnIncomingCall(call, null); } } } - mxPendingIncomingCallId.clear(); } + mxPendingIncomingCallId.clear(); }); } @@ -661,7 +596,7 @@ public void createCallInRoom(final String roomId, final boolean isVideo, final A // when a room is encrypted, test first there is no unknown device // else the call will fail. // So it seems safer to reject the call creation it it will fail. - if (room.isEncrypted() && mSession.getCrypto().warnOnUnknownDevices()) { + if (room.isEncrypted() && mSession.getCrypto() != null && mSession.getCrypto().warnOnUnknownDevices()) { room.getJoinedMembersAsync(new SimpleApiCallback>(callback) { @Override public void onSuccess(List members) { @@ -684,12 +619,7 @@ public void onSuccess(Void anything) { dispatchOnOutgoingCall(call); if (null != callback) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - callback.onSuccess(call); - } - }); + mUIThreadHandler.post(() -> callback.onSuccess(call)); } } }); @@ -702,12 +632,7 @@ public void run() { call.setRooms(room, room); if (null != callback) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - callback.onSuccess(call); - } - }); + mUIThreadHandler.post(() -> callback.onSuccess(call)); } } } else { @@ -731,12 +656,7 @@ public void onSuccess(Room conferenceRoom) { dispatchOnOutgoingCall(call); if (null != callback) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - callback.onSuccess(call); - } - }); + mUIThreadHandler.post(() -> callback.onSuccess(call)); } } @@ -880,99 +800,94 @@ private void refreshTurnServer() { Log.d(LOG_TAG, "## refreshTurnServer () starts"); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - mCallResClient.getTurnServer(new ApiCallback() { - private void restartAfter(int msDelay) { - // reported by GA - // "ttl" seems invalid - if (msDelay <= 0) { - Log.e(LOG_TAG, "## refreshTurnServer() : invalid delay " + msDelay); - } else { - if (null != mTurnServerTimer) { - mTurnServerTimer.cancel(); - } - - try { - mTurnServerTimer = new Timer(); - mTurnServerTimer.schedule(new TimerTask() { - @Override - public void run() { - if (mTurnServerTimer != null) { - mTurnServerTimer.cancel(); - mTurnServerTimer = null; - } - - refreshTurnServer(); - } - }, msDelay); - } catch (Throwable e) { - Log.e(LOG_TAG, "## refreshTurnServer() failed to start the timer", e); + mUIThreadHandler.post(() -> mCallResClient.getTurnServer(new ApiCallback() { + private void restartAfter(int msDelay) { + // reported by GA + // "ttl" seems invalid + if (msDelay <= 0) { + Log.e(LOG_TAG, "## refreshTurnServer() : invalid delay " + msDelay); + } else { + if (null != mTurnServerTimer) { + mTurnServerTimer.cancel(); + } - if (null != mTurnServerTimer) { + try { + mTurnServerTimer = new Timer(); + mTurnServerTimer.schedule(new TimerTask() { + @Override + public void run() { + if (mTurnServerTimer != null) { mTurnServerTimer.cancel(); mTurnServerTimer = null; } + refreshTurnServer(); } + }, msDelay); + } catch (Throwable e) { + Log.e(LOG_TAG, "## refreshTurnServer() failed to start the timer", e); + + if (null != mTurnServerTimer) { + mTurnServerTimer.cancel(); + mTurnServerTimer = null; } + refreshTurnServer(); } + } + } - @Override - public void onSuccess(JsonObject info) { - // privacy - Log.d(LOG_TAG, "## refreshTurnServer () : onSuccess"); - //Log.d(LOG_TAG, "onSuccess " + info); - - if (null != info) { - if (info.has("uris")) { - synchronized (LOG_TAG) { - mTurnServer = info; - } - } - - if (info.has("ttl")) { - int ttl = 60000; + @Override + public void onSuccess(JsonObject info) { + // privacy + Log.d(LOG_TAG, "## refreshTurnServer () : onSuccess"); + //Log.d(LOG_TAG, "onSuccess " + info); + + if (null != info) { + if (info.has("uris")) { + synchronized (LOG_TAG) { + mTurnServer = info; + } + } - try { - ttl = info.get("ttl").getAsInt(); - // restart a 90 % before ttl expires - ttl = ttl * 9 / 10; - } catch (Exception e) { - Log.e(LOG_TAG, "Fail to retrieve ttl " + e.getMessage(), e); - } + if (info.has("ttl")) { + int ttl = 60000; - Log.d(LOG_TAG, "## refreshTurnServer () : onSuccess : retry after " + ttl + " seconds"); - restartAfter(ttl * 1000); - } + try { + ttl = info.get("ttl").getAsInt(); + // restart a 90 % before ttl expires + ttl = ttl * 9 / 10; + } catch (Exception e) { + Log.e(LOG_TAG, "Fail to retrieve ttl " + e.getMessage(), e); } - } - @Override - public void onNetworkError(Exception e) { - Log.e(LOG_TAG, "## refreshTurnServer () : onNetworkError", e); - restartAfter(60000); + Log.d(LOG_TAG, "## refreshTurnServer () : onSuccess : retry after " + ttl + " seconds"); + restartAfter(ttl * 1000); } + } + } - @Override - public void onMatrixError(MatrixError e) { - Log.e(LOG_TAG, "## refreshTurnServer () : onMatrixError() : " + e.errcode); + @Override + public void onNetworkError(Exception e) { + Log.e(LOG_TAG, "## refreshTurnServer () : onNetworkError", e); + restartAfter(60000); + } - if (TextUtils.equals(e.errcode, MatrixError.LIMIT_EXCEEDED) && (null != e.retry_after_ms)) { - Log.e(LOG_TAG, "## refreshTurnServer () : onMatrixError() : retry after " + e.retry_after_ms + " ms"); - restartAfter(e.retry_after_ms); - } - } + @Override + public void onMatrixError(MatrixError e) { + Log.e(LOG_TAG, "## refreshTurnServer () : onMatrixError() : " + e.errcode); - @Override - public void onUnexpectedError(Exception e) { - // should never happen - Log.e(LOG_TAG, "## refreshTurnServer () : onUnexpectedError()", e); - } - }); + if (TextUtils.equals(e.errcode, MatrixError.LIMIT_EXCEEDED) && (null != e.retry_after_ms)) { + Log.e(LOG_TAG, "## refreshTurnServer () : onMatrixError() : retry after " + e.retry_after_ms + " ms"); + restartAfter(e.retry_after_ms); + } } - }); + + @Override + public void onUnexpectedError(Exception e) { + // should never happen + Log.e(LOG_TAG, "## refreshTurnServer () : onUnexpectedError()", e); + } + })); } //============================================================================================================== @@ -1069,12 +984,7 @@ private void inviteConferenceUser(final Room room, final ApiCallback callb RoomMember conferenceMember = room.getMember(conferenceUserId); if ((null != conferenceMember) && TextUtils.equals(conferenceMember.membership, RoomMember.MEMBERSHIP_JOIN)) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - callback.onSuccess(null); - } - }); + mUIThreadHandler.post(() -> callback.onSuccess(null)); } else { room.invite(mSession, conferenceUserId, callback); } diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcCall.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcCall.java index ab364f1e2..821a327bb 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcCall.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcCall.java @@ -58,6 +58,7 @@ import org.webrtc.VideoTrack; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Timer; import java.util.TimerTask; @@ -78,10 +79,10 @@ public class MXWebRtcCall extends MXCall { private static final int CAMERA_TYPE_REAR = 2; private static final int CAMERA_TYPE_UNDEFINED = -1; - static private PeerConnectionFactory mPeerConnectionFactory = null; - static private String mFrontCameraName = null; - static private String mBackCameraName = null; - static private CameraVideoCapturer mCameraVideoCapturer = null; + private static PeerConnectionFactory mPeerConnectionFactory = null; + private static String mFrontCameraName = null; + private static String mBackCameraName = null; + private static CameraVideoCapturer mCameraVideoCapturer = null; private RelativeLayout mCallView = null; @@ -233,7 +234,7 @@ public MXWebRtcCall(MXSession session, Context context, JsonElement turnServer, if (!defaultIceServerUri.startsWith("stun:")) { defaultIceServerUri = "stun:" + defaultIceServerUri; } - defaultIceServer = new PeerConnection.IceServer(defaultIceServerUri); + defaultIceServer = PeerConnection.IceServer.builder(defaultIceServerUri).createIceServer(); } catch (Throwable e) { Log.e(LOG_TAG, "MXWebRtcCall constructor invalid default stun" + defaultIceServerUri); } @@ -257,9 +258,6 @@ private static void initializeAndroidGlobals(Context context) { PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions()); mIsInitialized = true; - - PeerConnectionFactory.initializeFieldTrials(null); - mIsSupported = true; Log.d(LOG_TAG, "## initializeAndroidGlobals(): mIsInitialized=" + mIsInitialized); } catch (Throwable e) { @@ -281,25 +279,19 @@ public void createCallView() { Log.d(LOG_TAG, "++ createCallView()"); dispatchOnStateDidChange(CALL_STATE_CREATING_CALL_VIEW); - mUIThreadHandler.postDelayed(new Runnable() { - @Override - public void run() { - mCallView = new RelativeLayout(mContext); - mCallView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, - RelativeLayout.LayoutParams.MATCH_PARENT)); - mCallView.setBackgroundColor(ContextCompat.getColor(mContext, android.R.color.black)); - mCallView.setVisibility(View.GONE); - - dispatchOnCallViewCreated(mCallView); - - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - dispatchOnStateDidChange(CALL_STATE_READY); - dispatchOnReady(); - } - }); - } + mUIThreadHandler.postDelayed(() -> { + mCallView = new RelativeLayout(mContext); + mCallView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.MATCH_PARENT)); + mCallView.setBackgroundColor(ContextCompat.getColor(mContext, android.R.color.black)); + mCallView.setVisibility(View.GONE); + + dispatchOnCallViewCreated(mCallView); + + mUIThreadHandler.post(() -> { + dispatchOnStateDidChange(CALL_STATE_READY); + dispatchOnReady(); + }); }, 10); } } @@ -345,29 +337,20 @@ private void terminate(final int endCallReasonId) { // mPeerConnectionFactory is static so it might be used by another call // so we test that the current has been created if (isPeerConnectionFactoryAllowed && (null != mPeerConnectionFactory)) { - mPeerConnectionFactory.dispose(); + Log.w(LOG_TAG, "Call to mPeerConnectionFactory.dispose() has been removed"); + // mPeerConnectionFactory.dispose(); mPeerConnectionFactory = null; } if (null != mCallView) { final View fCallView = mCallView; - fCallView.post(new Runnable() { - @Override - public void run() { - fCallView.setVisibility(View.GONE); - } - }); + fCallView.post(() -> fCallView.setVisibility(View.GONE)); mCallView = null; } - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - dispatchOnCallEnd(endCallReasonId); - } - }); + mUIThreadHandler.post(() -> dispatchOnCallEnd(endCallReasonId)); } /** @@ -587,13 +570,12 @@ private void createLocalStream() { } // define constraints - MediaConstraints pcConstraints = new MediaConstraints(); - pcConstraints.optional.add(new MediaConstraints.KeyValuePair("RtpDataChannels", "true")); + PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers); + rtcConfig.enableRtpDataChannel = true; // start connecting to the other peer by creating the peer connection mPeerConnection = mPeerConnectionFactory.createPeerConnection( - iceServers, - pcConstraints, + rtcConfig, new PeerConnection.Observer() { @Override public void onSignalingChange(PeerConnection.SignalingState signalingState) { @@ -603,53 +585,47 @@ public void onSignalingChange(PeerConnection.SignalingState signalingState) { @Override public void onIceConnectionChange(final PeerConnection.IceConnectionState iceConnectionState) { Log.d(LOG_TAG, "## mPeerConnection creation: onIceConnectionChange " + iceConnectionState); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED) { - if ((null != mLocalVideoTrack) && mUsingLargeLocalRenderer && isVideo()) { - mLocalVideoTrack.setEnabled(false); - - // in conference call, there is no local preview, - // the local attendee video is sent by the server among the others conference attendees. - if (!isConference()) { - // add local preview, only for 1:1 call - //mLocalVideoTrack.addRenderer(mSmallLocalRenderer); - mPipRTCView.setStream(mLocalMediaStream); - mPipRTCView.setVisibility(View.VISIBLE); - - // to be able to display the avatar video above the large one - mPipRTCView.setZOrder(1); - } - - mLocalVideoTrack.setEnabled(true); - mUsingLargeLocalRenderer = false; - - mCallView.post(new Runnable() { - @Override - public void run() { - if (null != mCallView) { - mCallView.invalidate(); - } - } - }); + mUIThreadHandler.post(() -> { + if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED) { + if ((null != mLocalVideoTrack) && mUsingLargeLocalRenderer && isVideo()) { + mLocalVideoTrack.setEnabled(false); + + // in conference call, there is no local preview, + // the local attendee video is sent by the server among the others conference attendees. + if (!isConference()) { + // add local preview, only for 1:1 call + //mLocalVideoTrack.addRenderer(mSmallLocalRenderer); + mPipRTCView.setStream(mLocalMediaStream); + mPipRTCView.setVisibility(View.VISIBLE); + + // to be able to display the avatar video above the large one + mPipRTCView.setZOrder(1); } - dispatchOnStateDidChange(IMXCall.CALL_STATE_CONNECTED); - } - // theses states are ignored - // only the matrix hangup event is managed - /*else if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) { - // TODO warn the user ? - hangup(null); - } else if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) { - // TODO warn the user ? - terminate(); - }*/ - else if (iceConnectionState == PeerConnection.IceConnectionState.FAILED) { - dispatchOnCallError(CALL_ERROR_ICE_FAILED); - hangup("ice_failed"); + mLocalVideoTrack.setEnabled(true); + mUsingLargeLocalRenderer = false; + + mCallView.post(() -> { + if (null != mCallView) { + mCallView.invalidate(); + } + }); } + + dispatchOnStateDidChange(IMXCall.CALL_STATE_CONNECTED); + } + // theses states are ignored + // only the matrix hangup event is managed + /*else if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) { + // TODO warn the user ? + hangup(null); + } else if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) { + // TODO warn the user ? + terminate(); + }*/ + else if (iceConnectionState == PeerConnection.IceConnectionState.FAILED) { + dispatchOnCallError(CALL_ERROR_ICE_FAILED); + hangup("ice_failed"); } }); } @@ -661,7 +637,7 @@ public void onIceConnectionReceivingChange(boolean var1) { @Override public void onIceCandidatesRemoved(IceCandidate[] var1) { - Log.d(LOG_TAG, "## mPeerConnection creation: onIceCandidatesRemoved " + var1); + Log.d(LOG_TAG, "## mPeerConnection creation: onIceCandidatesRemoved " + Arrays.toString(var1)); } @Override @@ -671,69 +647,66 @@ public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringSt @Override public void onAddTrack(RtpReceiver var1, MediaStream[] var2) { - Log.d(LOG_TAG, "## mPeerConnection creation: onAddTrack " + var1 + " -- " + var2); + Log.d(LOG_TAG, "## mPeerConnection creation: onAddTrack " + var1 + " -- " + Arrays.toString(var2)); } @Override public void onIceCandidate(final IceCandidate iceCandidate) { Log.d(LOG_TAG, "## mPeerConnection creation: onIceCandidate " + iceCandidate); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (!isCallEnded()) { - JsonObject content = new JsonObject(); - content.addProperty("version", 0); - content.addProperty("call_id", mCallId); - - JsonArray candidates = new JsonArray(); - JsonObject cand = new JsonObject(); - cand.addProperty("sdpMLineIndex", iceCandidate.sdpMLineIndex); - cand.addProperty("sdpMid", iceCandidate.sdpMid); - cand.addProperty("candidate", iceCandidate.sdp); - candidates.add(cand); - content.add("candidates", candidates); - - boolean addIt = true; - - // merge candidates - if (mPendingEvents.size() > 0) { - try { - Event lastEvent = mPendingEvents.get(mPendingEvents.size() - 1); - - if (TextUtils.equals(lastEvent.getType(), Event.EVENT_TYPE_CALL_CANDIDATES)) { - // return the content cast as a JsonObject - // it is not a copy - JsonObject lastContent = lastEvent.getContentAsJsonObject(); - - JsonArray lastContentCandidates = lastContent.get("candidates").getAsJsonArray(); - JsonArray newContentCandidates = content.get("candidates").getAsJsonArray(); - - Log.d(LOG_TAG, "Merge candidates from " + lastContentCandidates.size() - + " to " + (lastContentCandidates.size() + newContentCandidates.size() + " items.")); - - lastContentCandidates.addAll(newContentCandidates); - - // replace the candidates list - lastContent.remove("candidates"); - lastContent.add("candidates", lastContentCandidates); - - // don't need to save anything, lastContent is a reference not a copy - addIt = false; - } - } catch (Exception e) { - Log.e(LOG_TAG, "## createLocalStream(): createPeerConnection - onIceCandidate() Exception Msg=" - + e.getMessage(), e); + mUIThreadHandler.post(() -> { + if (!isCallEnded()) { + JsonObject content = new JsonObject(); + content.addProperty("version", 0); + content.addProperty("call_id", mCallId); + + JsonArray candidates = new JsonArray(); + JsonObject cand = new JsonObject(); + cand.addProperty("sdpMLineIndex", iceCandidate.sdpMLineIndex); + cand.addProperty("sdpMid", iceCandidate.sdpMid); + cand.addProperty("candidate", iceCandidate.sdp); + candidates.add(cand); + content.add("candidates", candidates); + + boolean addIt = true; + + // merge candidates + if (mPendingEvents.size() > 0) { + try { + Event lastEvent = mPendingEvents.get(mPendingEvents.size() - 1); + + if (TextUtils.equals(lastEvent.getType(), Event.EVENT_TYPE_CALL_CANDIDATES)) { + // return the content cast as a JsonObject + // it is not a copy + JsonObject lastContent = lastEvent.getContentAsJsonObject(); + + JsonArray lastContentCandidates = lastContent.get("candidates").getAsJsonArray(); + JsonArray newContentCandidates = content.get("candidates").getAsJsonArray(); + + Log.d(LOG_TAG, "Merge candidates from " + lastContentCandidates.size() + + " to " + (lastContentCandidates.size() + newContentCandidates.size() + " items.")); + + lastContentCandidates.addAll(newContentCandidates); + + // replace the candidates list + lastContent.remove("candidates"); + lastContent.add("candidates", lastContentCandidates); + + // don't need to save anything, lastContent is a reference not a copy + addIt = false; } + } catch (Exception e) { + Log.e(LOG_TAG, "## createLocalStream(): createPeerConnection - onIceCandidate() Exception Msg=" + + e.getMessage(), e); } + } - if (addIt) { - Event event = new Event(Event.EVENT_TYPE_CALL_CANDIDATES, content, mSession.getCredentials().userId, - mCallSignalingRoom.getRoomId()); + if (addIt) { + Event event = new Event(Event.EVENT_TYPE_CALL_CANDIDATES, content, mSession.getCredentials().userId, + mCallSignalingRoom.getRoomId()); - mPendingEvents.add(event); - sendNextEvent(); - } + mPendingEvents.add(event); + sendNextEvent(); } } }); @@ -743,15 +716,12 @@ public void run() { public void onAddStream(final MediaStream mediaStream) { Log.d(LOG_TAG, "## mPeerConnection creation: onAddStream " + mediaStream); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if ((mediaStream.videoTracks.size() == 1) && !isCallEnded()) { - mRemoteVideoTrack = mediaStream.videoTracks.get(0); - mRemoteVideoTrack.setEnabled(true); - mFullScreenRTCView.setStream(mediaStream); - mFullScreenRTCView.setVisibility(View.VISIBLE); - } + mUIThreadHandler.post(() -> { + if ((mediaStream.videoTracks.size() == 1) && !isCallEnded()) { + mRemoteVideoTrack = mediaStream.videoTracks.get(0); + mRemoteVideoTrack.setEnabled(true); + mFullScreenRTCView.setStream(mediaStream); + mFullScreenRTCView.setVisibility(View.VISIBLE); } }); } @@ -760,14 +730,11 @@ public void run() { public void onRemoveStream(final MediaStream mediaStream) { Log.d(LOG_TAG, "## mPeerConnection creation: onRemoveStream " + mediaStream); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (null != mRemoteVideoTrack) { - mRemoteVideoTrack.dispose(); - mRemoteVideoTrack = null; - mediaStream.videoTracks.get(0).dispose(); - } + mUIThreadHandler.post(() -> { + if (null != mRemoteVideoTrack) { + mRemoteVideoTrack.dispose(); + mRemoteVideoTrack = null; + mediaStream.videoTracks.get(0).dispose(); } }); @@ -808,39 +775,36 @@ public void onCreateSuccess(SessionDescription sessionDescription) { final SessionDescription sdp = new SessionDescription(sessionDescription.type, sessionDescription.description); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (mPeerConnection != null) { - // must be done to before sending the invitation message - mPeerConnection.setLocalDescription(new SdpObserver() { - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - Log.d(LOG_TAG, "setLocalDescription onCreateSuccess"); - } + mUIThreadHandler.post(() -> { + if (mPeerConnection != null) { + // must be done to before sending the invitation message + mPeerConnection.setLocalDescription(new SdpObserver() { + @Override + public void onCreateSuccess(SessionDescription sessionDescription1) { + Log.d(LOG_TAG, "setLocalDescription onCreateSuccess"); + } - @Override - public void onSetSuccess() { - Log.d(LOG_TAG, "setLocalDescription onSetSuccess"); - sendInvite(sdp); - dispatchOnStateDidChange(IMXCall.CALL_STATE_INVITE_SENT); - } + @Override + public void onSetSuccess() { + Log.d(LOG_TAG, "setLocalDescription onSetSuccess"); + sendInvite(sdp); + dispatchOnStateDidChange(IMXCall.CALL_STATE_INVITE_SENT); + } - @Override - public void onCreateFailure(String s) { - Log.e(LOG_TAG, "setLocalDescription onCreateFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - hangup(null); - } + @Override + public void onCreateFailure(String s) { + Log.e(LOG_TAG, "setLocalDescription onCreateFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + hangup(null); + } - @Override - public void onSetFailure(String s) { - Log.e(LOG_TAG, "setLocalDescription onSetFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - hangup(null); - } - }, sdp); - } + @Override + public void onSetFailure(String s) { + Log.e(LOG_TAG, "setLocalDescription onSetFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + hangup(null); + } + }, sdp); } }); } @@ -1066,45 +1030,42 @@ private void initCallUI(final JsonObject callInviteParams, VideoLayoutConfigurat if (isVideo()) { Log.d(LOG_TAG, "## initCallUI(): building UI video call"); try { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (null == mPeerConnectionFactory) { - Log.d(LOG_TAG, "## initCallUI(): video call and no mPeerConnectionFactory"); - - // Inspired from https://vivekc.xyz/getting-started-with-webrtc-part-4-de72b58ab31e - //Create a new PeerConnectionFactory instance - using Hardware encoder and decoder. - PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); - DefaultVideoEncoderFactory defaultVideoEncoderFactory = - new DefaultVideoEncoderFactory( - EglUtils.getRootEglBase().getEglBaseContext(), - /* enableIntelVp8Encoder */true, - /* enableH264HighProfile */true); - DefaultVideoDecoderFactory defaultVideoDecoderFactory = - new DefaultVideoDecoderFactory(EglUtils.getRootEglBase().getEglBaseContext()); - - mPeerConnectionFactory = PeerConnectionFactory.builder() - .setOptions(options) - .setVideoEncoderFactory(defaultVideoEncoderFactory) - .setVideoDecoderFactory(defaultVideoDecoderFactory) - .createPeerConnectionFactory(); - - // Initialize EGL contexts required for HW acceleration. - /* - EglBase.Context eglContext = EglUtils.getRootEglBaseContext(); - if (eglContext != null) { - mPeerConnectionFactory.setVideoHwAccelerationOptions(eglContext, eglContext); - } - */ + mUIThreadHandler.post(() -> { + if (null == mPeerConnectionFactory) { + Log.d(LOG_TAG, "## initCallUI(): video call and no mPeerConnectionFactory"); + + // Inspired from https://vivekc.xyz/getting-started-with-webrtc-part-4-de72b58ab31e + //Create a new PeerConnectionFactory instance - using Hardware encoder and decoder. + PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); + DefaultVideoEncoderFactory defaultVideoEncoderFactory = + new DefaultVideoEncoderFactory( + EglUtils.getRootEglBase().getEglBaseContext(), + /* enableIntelVp8Encoder */true, + /* enableH264HighProfile */true); + DefaultVideoDecoderFactory defaultVideoDecoderFactory = + new DefaultVideoDecoderFactory(EglUtils.getRootEglBase().getEglBaseContext()); + + mPeerConnectionFactory = PeerConnectionFactory.builder() + .setOptions(options) + .setVideoEncoderFactory(defaultVideoEncoderFactory) + .setVideoDecoderFactory(defaultVideoDecoderFactory) + .createPeerConnectionFactory(); + + // Initialize EGL contexts required for HW acceleration. + /* + EglBase.Context eglContext = EglUtils.getRootEglBaseContext(); + if (eglContext != null) { + mPeerConnectionFactory.setVideoHwAccelerationOptions(eglContext, eglContext); + } + */ - createVideoTrack(); - createAudioTrack(); - createLocalStream(); + createVideoTrack(); + createAudioTrack(); + createLocalStream(); - if (null != callInviteParams) { - dispatchOnStateDidChange(CALL_STATE_RINGING); - setRemoteDescription(callInviteParams); - } + if (null != callInviteParams) { + dispatchOnStateDidChange(CALL_STATE_RINGING); + setRemoteDescription(callInviteParams); } } }); @@ -1150,18 +1111,15 @@ public void run() { Log.d(LOG_TAG, "## initCallUI(): build audio call"); // audio call - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (null == mPeerConnectionFactory) { - mPeerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory(); - createAudioTrack(); - createLocalStream(); - - if (null != callInviteParams) { - dispatchOnStateDidChange(CALL_STATE_RINGING); - setRemoteDescription(callInviteParams); - } + mUIThreadHandler.post(() -> { + if (null == mPeerConnectionFactory) { + mPeerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory(); + createAudioTrack(); + createLocalStream(); + + if (null != callInviteParams) { + dispatchOnStateDidChange(CALL_STATE_RINGING); + setRemoteDescription(callInviteParams); } } }); @@ -1262,12 +1220,7 @@ public void onCreateSuccess(SessionDescription sessionDescription) { public void onSetSuccess() { Log.d(LOG_TAG, "setRemoteDescription onSetSuccess"); mIsIncomingPrepared = true; - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - checkPendingCandidates(); - } - }); + mUIThreadHandler.post(() -> checkPendingCandidates()); } @Override @@ -1301,12 +1254,7 @@ public void prepareIncomingCall(final JsonObject aCallInviteParams, final String dispatchOnStateDidChange(CALL_STATE_WAIT_LOCAL_MEDIA); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - initCallUI(aCallInviteParams, aLocalVideoPosition); - } - }); + mUIThreadHandler.post(() -> initCallUI(aCallInviteParams, aLocalVideoPosition)); } else if (CALL_STATE_CREATED.equals(getCallState())) { mCallInviteParams = aCallInviteParams; @@ -1348,54 +1296,51 @@ private void onCallAnswer(final Event event) { Log.d(LOG_TAG, "onCallAnswer : call state " + getCallState()); if (!CALL_STATE_CREATED.equals(getCallState()) && (null != mPeerConnection)) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - dispatchOnStateDidChange(IMXCall.CALL_STATE_CONNECTING); - SessionDescription aDescription = null; + mUIThreadHandler.post(() -> { + dispatchOnStateDidChange(IMXCall.CALL_STATE_CONNECTING); + SessionDescription aDescription = null; - // extract the description - try { - JsonObject eventContent = event.getContentAsJsonObject(); + // extract the description + try { + JsonObject eventContent = event.getContentAsJsonObject(); - if (eventContent.has("answer")) { - JsonObject answer = eventContent.getAsJsonObject("answer"); - String type = answer.get("type").getAsString(); - String sdp = answer.get("sdp").getAsString(); + if (eventContent.has("answer")) { + JsonObject answer = eventContent.getAsJsonObject("answer"); + String type = answer.get("type").getAsString(); + String sdp = answer.get("sdp").getAsString(); - if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(sdp) && type.equals("answer")) { - aDescription = new SessionDescription(SessionDescription.Type.ANSWER, sdp); - } + if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(sdp) && type.equals("answer")) { + aDescription = new SessionDescription(SessionDescription.Type.ANSWER, sdp); } - - } catch (Exception e) { - Log.e(LOG_TAG, "onCallAnswer : " + e.getMessage(), e); } - mPeerConnection.setRemoteDescription(new SdpObserver() { - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - Log.d(LOG_TAG, "setRemoteDescription onCreateSuccess"); - } + } catch (Exception e) { + Log.e(LOG_TAG, "onCallAnswer : " + e.getMessage(), e); + } - @Override - public void onSetSuccess() { - Log.d(LOG_TAG, "setRemoteDescription onSetSuccess"); - } + mPeerConnection.setRemoteDescription(new SdpObserver() { + @Override + public void onCreateSuccess(SessionDescription sessionDescription) { + Log.d(LOG_TAG, "setRemoteDescription onCreateSuccess"); + } - @Override - public void onCreateFailure(String s) { - Log.e(LOG_TAG, "setRemoteDescription onCreateFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - } + @Override + public void onSetSuccess() { + Log.d(LOG_TAG, "setRemoteDescription onSetSuccess"); + } - @Override - public void onSetFailure(String s) { - Log.e(LOG_TAG, "setRemoteDescription onSetFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - } - }, aDescription); - } + @Override + public void onCreateFailure(String s) { + Log.e(LOG_TAG, "setRemoteDescription onCreateFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + } + + @Override + public void onSetFailure(String s) { + Log.e(LOG_TAG, "setRemoteDescription onSetFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + } + }, aDescription); }); } } @@ -1410,22 +1355,12 @@ private void onCallHangup(final int hangUpReasonId) { String state = getCallState(); if (!CALL_STATE_CREATED.equals(state) && (null != mPeerConnection)) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - terminate(hangUpReasonId); - } - }); + mUIThreadHandler.post(() -> terminate(hangUpReasonId)); } else if (CALL_STATE_WAIT_LOCAL_MEDIA.equals(state) && isVideo()) { // specific case fixing: a video call hung up by the calling side // when the callee is still displaying the InComingCallActivity dialog. // If terminate() was not called, the dialog was never dismissed. - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - terminate(hangUpReasonId); - } - }); + mUIThreadHandler.post(() -> terminate(hangUpReasonId)); } } @@ -1524,22 +1459,12 @@ public void handleCallEvent(Event event) { switch (eventType) { case Event.EVENT_TYPE_CALL_INVITE: // warn in the UI thread - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - dispatchOnStateDidChange(CALL_STATE_RINGING); - } - }); + mUIThreadHandler.post(() -> dispatchOnStateDidChange(CALL_STATE_RINGING)); break; case Event.EVENT_TYPE_CALL_ANSWER: // call answered from another device - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - onAnsweredElsewhere(); - } - }); + mUIThreadHandler.post(this::onAnsweredElsewhere); break; case Event.EVENT_TYPE_CALL_HANGUP: @@ -1565,85 +1490,79 @@ public void answer() { Log.d(LOG_TAG, "answer " + getCallState()); if (!CALL_STATE_CREATED.equals(getCallState()) && (null != mPeerConnection)) { - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (null == mPeerConnection) { - Log.d(LOG_TAG, "answer the connection has been closed"); - return; - } + mUIThreadHandler.post(() -> { + if (null == mPeerConnection) { + Log.d(LOG_TAG, "answer the connection has been closed"); + return; + } - dispatchOnStateDidChange(CALL_STATE_CREATE_ANSWER); + dispatchOnStateDidChange(CALL_STATE_CREATE_ANSWER); - MediaConstraints constraints = new MediaConstraints(); - constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")); - constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", isVideo() ? "true" : "false")); + MediaConstraints constraints = new MediaConstraints(); + constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")); + constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", isVideo() ? "true" : "false")); - mPeerConnection.createAnswer(new SdpObserver() { - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - Log.d(LOG_TAG, "createAnswer onCreateSuccess"); + mPeerConnection.createAnswer(new SdpObserver() { + @Override + public void onCreateSuccess(SessionDescription sessionDescription) { + Log.d(LOG_TAG, "createAnswer onCreateSuccess"); - final SessionDescription sdp = new SessionDescription(sessionDescription.type, sessionDescription.description); + final SessionDescription sdp = new SessionDescription(sessionDescription.type, sessionDescription.description); - mUIThreadHandler.post(new Runnable() { - @Override - public void run() { - if (mPeerConnection != null) { - // must be done to before sending the invitation message - mPeerConnection.setLocalDescription(new SdpObserver() { - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - Log.d(LOG_TAG, "setLocalDescription onCreateSuccess"); - } - - @Override - public void onSetSuccess() { - Log.d(LOG_TAG, "setLocalDescription onSetSuccess"); - sendAnswer(sdp); - dispatchOnStateDidChange(IMXCall.CALL_STATE_CONNECTING); - } - - @Override - public void onCreateFailure(String s) { - Log.e(LOG_TAG, "setLocalDescription onCreateFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - hangup(null); - } - - @Override - public void onSetFailure(String s) { - Log.e(LOG_TAG, "setLocalDescription onSetFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - hangup(null); - } - }, sdp); + mUIThreadHandler.post(() -> { + if (mPeerConnection != null) { + // must be done to before sending the invitation message + mPeerConnection.setLocalDescription(new SdpObserver() { + @Override + public void onCreateSuccess(SessionDescription sessionDescription1) { + Log.d(LOG_TAG, "setLocalDescription onCreateSuccess"); } - } - }); - } - @Override - public void onSetSuccess() { - Log.d(LOG_TAG, "createAnswer onSetSuccess"); - } + @Override + public void onSetSuccess() { + Log.d(LOG_TAG, "setLocalDescription onSetSuccess"); + sendAnswer(sdp); + dispatchOnStateDidChange(IMXCall.CALL_STATE_CONNECTING); + } - @Override - public void onCreateFailure(String s) { - Log.e(LOG_TAG, "createAnswer onCreateFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - hangup(null); - } + @Override + public void onCreateFailure(String s) { + Log.e(LOG_TAG, "setLocalDescription onCreateFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + hangup(null); + } - @Override - public void onSetFailure(String s) { - Log.e(LOG_TAG, "createAnswer onSetFailure " + s); - dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); - hangup(null); - } - }, constraints); + @Override + public void onSetFailure(String s) { + Log.e(LOG_TAG, "setLocalDescription onSetFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + hangup(null); + } + }, sdp); + } + }); + } + + @Override + public void onSetSuccess() { + Log.d(LOG_TAG, "createAnswer onSetSuccess"); + } + + @Override + public void onCreateFailure(String s) { + Log.e(LOG_TAG, "createAnswer onCreateFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + hangup(null); + } + + @Override + public void onSetFailure(String s) { + Log.e(LOG_TAG, "createAnswer onSetFailure " + s); + dispatchOnCallError(CALL_ERROR_CAMERA_INIT_FAILED); + hangup(null); + } + }, constraints); - } }); } diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcView.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcView.java index a8809669b..65acbabf6 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcView.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/MXWebRtcView.java @@ -132,12 +132,7 @@ public void onFrameResolutionChanged( * initializing new instances on every (method) call. */ private final Runnable requestSurfaceViewRendererLayoutRunnable - = new Runnable() { - @Override - public void run() { - requestSurfaceViewRendererLayout(); - } - }; + = this::requestSurfaceViewRendererLayout; /** * The scaling type this {@code WebRTCView} is to apply to the video @@ -147,7 +142,7 @@ public void run() { private ScalingType scalingType; /** - * The {@link View} and {@link VideoRenderer} implementation which + * The {@link View} and {@link org.webrtc.VideoSink} implementation which * actually renders {@link #videoTrack} on behalf of this instance. */ private final SurfaceViewRenderer surfaceViewRenderer; @@ -177,7 +172,7 @@ public MXWebRtcView(Context context) { * * @return The {@code SurfaceViewRenderer} which renders {@code videoTrack}. */ - private final SurfaceViewRenderer getSurfaceViewRenderer() { + private SurfaceViewRenderer getSurfaceViewRenderer() { return surfaceViewRenderer; } @@ -298,8 +293,6 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { scalingType = this.scalingType; } - SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer(); - switch (scalingType) { case SCALE_ASPECT_FILL: // Fill this ViewGroup with surfaceViewRenderer and the latter diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/VideoLayoutConfiguration.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/VideoLayoutConfiguration.java index fa784036c..b987a1324 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/call/VideoLayoutConfiguration.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/call/VideoLayoutConfiguration.java @@ -16,6 +16,8 @@ package org.matrix.androidsdk.call; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; /** @@ -24,6 +26,7 @@ public class VideoLayoutConfiguration implements Serializable { public final static int INVALID_VALUE = -1; + @NotNull @Override public String toString() { return "VideoLayoutConfiguration{" + diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/core/VersionsUtil.kt b/matrix-sdk/src/main/java/org/matrix/androidsdk/core/VersionsUtil.kt index 6a07e0e2a..4bb6dd191 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/core/VersionsUtil.kt +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/core/VersionsUtil.kt @@ -23,13 +23,13 @@ import org.matrix.androidsdk.rest.model.Versions */ // MatrixClientServerAPIVersion -private const val VERSION_R0_0_1 = "r0_0_1" -private const val VERSION_R0_1_0 = "r0_1_0" -private const val VERSION_R0_2_0 = "r0_2_0" -private const val VERSION_R0_3_0 = "r0_3_0" -private const val VERSION_R0_4_0 = "r0_4_0" -private const val VERSION_R0_5_0 = "r0_5_0" -private const val VERSION_R0_6_0 = "r0_6_0" +private const val VERSION_R0_0_1 = "r0.0.1" +private const val VERSION_R0_1_0 = "r0.1.0" +private const val VERSION_R0_2_0 = "r0.2.0" +private const val VERSION_R0_3_0 = "r0.3.0" +private const val VERSION_R0_4_0 = "r0.4.0" +private const val VERSION_R0_5_0 = "r0.5.0" +private const val VERSION_R0_6_0 = "r0.6.0" // MatrixVersionsFeature private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members" diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/data/MyUser.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/data/MyUser.java index 786209509..ad52fa6d9 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/data/MyUser.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/data/MyUser.java @@ -21,11 +21,11 @@ import android.os.Handler; import android.os.Looper; -import org.matrix.androidsdk.MXSession; import org.matrix.androidsdk.core.Log; import org.matrix.androidsdk.core.callback.ApiCallback; import org.matrix.androidsdk.core.callback.SimpleApiCallback; import org.matrix.androidsdk.core.model.MatrixError; +import org.matrix.androidsdk.data.store.IMXStore; import org.matrix.androidsdk.rest.model.User; import org.matrix.androidsdk.rest.model.pid.ThirdPartyIdentifier; import org.matrix.androidsdk.rest.model.pid.ThreePid; @@ -74,7 +74,10 @@ public void updateDisplayName(final String displayName, final ApiCallback public void onSuccess(Void info) { // Update the object member before calling the given callback MyUser.this.displayname = displayName; - mDataHandler.getStore().setDisplayName(displayName, System.currentTimeMillis()); + IMXStore store = mDataHandler.getStore(); + if (store != null) { + store.setDisplayName(displayName, System.currentTimeMillis()); + } callback.onSuccess(info); } @@ -93,7 +96,10 @@ public void updateAvatarUrl(final String avatarUrl, final ApiCallback call public void onSuccess(Void info) { // Update the object member before calling the given callback setAvatarUrl(avatarUrl); - mDataHandler.getStore().setAvatarURL(avatarUrl, System.currentTimeMillis()); + IMXStore store = mDataHandler.getStore(); + if (store != null) { + store.setAvatarURL(avatarUrl, System.currentTimeMillis()); + } callback.onSuccess(info); } @@ -127,8 +133,9 @@ private void buildIdentifiersLists() { mEmailIdentifiers = new ArrayList<>(); mPhoneNumberIdentifiers = new ArrayList<>(); //NPE reported on playstore - if (mDataHandler.getStore() != null) { - List identifiers = mDataHandler.getStore().thirdPartyIdentifiers(); + IMXStore store = mDataHandler.getStore(); + if (store != null) { + List identifiers = store.thirdPartyIdentifiers(); for (ThirdPartyIdentifier identifier : identifiers) { switch (identifier.medium) { case ThreePid.MEDIUM_EMAIL: @@ -259,10 +266,13 @@ public void onSuccess(String anAvatarUrl) { if (mDataHandler.isAlive()) { // local value setAvatarUrl(anAvatarUrl); - // metadata file - mDataHandler.getStore().setAvatarURL(anAvatarUrl, System.currentTimeMillis()); - // user - mDataHandler.getStore().storeUser(MyUser.this); + IMXStore store = mDataHandler.getStore(); + if (store != null) { + // metadata file + store.setAvatarURL(anAvatarUrl, System.currentTimeMillis()); + // user + store.storeUser(MyUser.this); + } mIsAvatarRefreshed = true; @@ -314,8 +324,9 @@ public void onSuccess(String aDisplayname) { // local value displayname = aDisplayname; // store metadata - if (mDataHandler.getStore() != null) { - mDataHandler.getStore().setDisplayName(aDisplayname, System.currentTimeMillis()); + IMXStore store = mDataHandler.getStore(); + if (store != null) { + store.setDisplayName(aDisplayname, System.currentTimeMillis()); } mIsDisplayNameRefreshed = true; @@ -366,7 +377,10 @@ public void refreshThirdPartyIdentifiers() { public void onSuccess(List identifiers) { if (mDataHandler.isAlive()) { // store - mDataHandler.getStore().setThirdPartyIdentifiers(identifiers); + IMXStore store = mDataHandler.getStore(); + if (store != null) { + store.setThirdPartyIdentifiers(identifiers); + } buildIdentifiersLists(); diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/data/Room.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/data/Room.java index abfccdd3c..8b02f7673 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/data/Room.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/data/Room.java @@ -60,7 +60,6 @@ import org.matrix.androidsdk.data.timeline.EventTimeline; import org.matrix.androidsdk.data.timeline.EventTimelineFactory; import org.matrix.androidsdk.db.MXMediaCache; -import org.matrix.androidsdk.features.identityserver.IdentityServerManager; import org.matrix.androidsdk.listeners.IMXEventListener; import org.matrix.androidsdk.listeners.MXEventListener; import org.matrix.androidsdk.listeners.MXRoomEventListener; @@ -93,7 +92,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -679,12 +677,12 @@ public void setOnInitialSyncCallback(ApiCallback callback) { * @param thirdPartySignedUrl the thirdPartySigned url * @param callback the callback */ - public void joinWithThirdPartySigned(final String alias, final String thirdPartySignedUrl, final ApiCallback callback) { + public void joinWithThirdPartySigned(final MXSession session, final String alias, final String thirdPartySignedUrl, final ApiCallback callback) { if (null == thirdPartySignedUrl) { join(alias, callback); } else { String url = thirdPartySignedUrl + "&mxid=" + mMyUserId; - UrlPostTask task = new UrlPostTask(); + UrlPostTask task = new UrlPostTask(session.getHomeServerConfig().getProxyConfig()); task.setListener(new UrlPostTask.IPostTaskListener() { @Override @@ -2317,7 +2315,7 @@ public void onUnexpectedError(Exception e) { if (Event.EVENT_TYPE_MESSAGE.equals(event.getType())) { mDataHandler.getDataRetriever().getRoomsRestClient() - .sendMessage(event.eventId, getRoomId(), JsonUtils.toMessage(event.getContent()), localCB); + .sendMessage(event.eventId, getRoomId(), event.getContentAsJsonObject(), localCB); } else { mDataHandler.getDataRetriever().getRoomsRestClient() .sendEventToRoom(event.eventId, getRoomId(), event.getType(), event.getContentAsJsonObject(), localCB); @@ -2431,7 +2429,7 @@ public void inviteByEmail(final MXSession session, String email, ApiCallback identifiers, ApiCallback callback) { if (null != identifiers) { - session.getIdentityServerManager().inviteInRoom(this,identifiers.iterator(),callback); + session.getIdentityServerManager().inviteInRoom(this, identifiers.iterator(), callback); } } diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaCache.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaCache.java index c170f6c89..89c85f42e 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaCache.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaCache.java @@ -26,11 +26,12 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; -import androidx.annotation.Nullable; import android.text.TextUtils; import android.webkit.MimeTypeMap; import android.widget.ImageView; +import androidx.annotation.Nullable; + import org.matrix.androidsdk.HomeServerConnectionConfig; import org.matrix.androidsdk.core.ContentManager; import org.matrix.androidsdk.core.FileContentUtils; @@ -78,7 +79,9 @@ public class MXMediaCache { private static final String MXMEDIA_STORE_IMAGES_FOLDER = "Images"; private static final String MXMEDIA_STORE_OTHERS_FOLDER = "Others"; private static final String MXMEDIA_STORE_TMP_FOLDER = "tmp"; - /**@deprecated*/ + /** + * @deprecated + */ private static final String MXMEDIA_STORE_SHARE_FOLDER = "share"; /** @@ -86,6 +89,11 @@ public class MXMediaCache { */ private ContentManager mContentManager; + /** + * The home server connection config + */ + private HomeServerConnectionConfig mHsConfig; + /** * The media folders list. */ @@ -118,7 +126,12 @@ public class MXMediaCache { * @param userID the account user Id. * @param context the context */ - public MXMediaCache(ContentManager contentManager, NetworkConnectivityReceiver networkConnectivityReceiver, String userID, Context context) { + public MXMediaCache(HomeServerConnectionConfig hsConfig, + ContentManager contentManager, + NetworkConnectivityReceiver networkConnectivityReceiver, + String userID, + Context context) { + mHsConfig = hsConfig; mContentManager = contentManager; mNetworkConnectivityReceiver = networkConnectivityReceiver; @@ -1432,7 +1445,8 @@ public void uploadContent(InputStream contentStream, String uploadId, IMXMediaUploadListener listener) { try { - new MXMediaUploadWorkerTask(mContentManager, + new MXMediaUploadWorkerTask(mHsConfig, + mContentManager, contentStream, mimeType, uploadId, diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaDownloadWorkerTask.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaDownloadWorkerTask.java index 0717ca6ac..1f94cffd8 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaDownloadWorkerTask.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaDownloadWorkerTask.java @@ -22,13 +22,14 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; -import androidx.annotation.Nullable; -import androidx.collection.LruCache; import android.text.TextUtils; import android.util.Pair; import android.webkit.MimeTypeMap; import android.widget.ImageView; +import androidx.annotation.Nullable; +import androidx.collection.LruCache; + import com.google.gson.JsonElement; import com.google.gson.JsonParser; @@ -63,6 +64,7 @@ import java.io.OutputStream; import java.lang.ref.WeakReference; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.security.MessageDigest; import java.util.ArrayList; @@ -737,7 +739,11 @@ protected JsonElement doInBackground(Void... params) { HttpURLConnection connection = null; try { - connection = (HttpURLConnection) url.openConnection(); + Proxy proxyConfig = mHsConfig.getProxyConfig(); + if (proxyConfig == null) { + proxyConfig = Proxy.NO_PROXY; + } + connection = (HttpURLConnection) url.openConnection(proxyConfig); if (RestClient.getUserAgent() != null) { connection.setRequestProperty("User-Agent", RestClient.getUserAgent()); diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaUploadWorkerTask.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaUploadWorkerTask.java index 30d251e01..5caa99089 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaUploadWorkerTask.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/db/MXMediaUploadWorkerTask.java @@ -22,6 +22,7 @@ import org.json.JSONException; import org.json.JSONObject; +import org.matrix.androidsdk.HomeServerConnectionConfig; import org.matrix.androidsdk.RestClient; import org.matrix.androidsdk.core.ContentManager; import org.matrix.androidsdk.core.JsonUtils; @@ -36,6 +37,7 @@ import java.io.EOFException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; @@ -87,6 +89,9 @@ public class MXMediaUploadWorkerTask extends AsyncTask { */ private boolean mIsDone; + // the home server connection config + private HomeServerConnectionConfig mHsConfig; + // upload const private static final int UPLOAD_BUFFER_READ_SIZE = 1024 * 32; @@ -161,6 +166,7 @@ public static void cancelPendingUploads() { /** * Constructor * + * @param hsConfig the home server connection config * @param contentManager the content manager * @param contentStream the stream to upload * @param mimeType the mime type @@ -168,7 +174,8 @@ public static void cancelPendingUploads() { * @param filename the dest filename * @param listener the upload listener */ - public MXMediaUploadWorkerTask(ContentManager contentManager, + public MXMediaUploadWorkerTask(HomeServerConnectionConfig hsConfig, + ContentManager contentManager, InputStream contentStream, String mimeType, String uploadId, @@ -185,6 +192,7 @@ public MXMediaUploadWorkerTask(ContentManager contentManager, } + mHsConfig = hsConfig; mContentManager = contentManager; mContentStream = contentStream; mMimeType = mimeType; @@ -296,7 +304,12 @@ protected String doInBackground(Void... params) { try { URL url = new URL(urlString); - conn = (HttpURLConnection) url.openConnection(); + Proxy proxyConfig = mHsConfig.getProxyConfig(); + if (proxyConfig == null) { + proxyConfig = Proxy.NO_PROXY; + } + + conn = (HttpURLConnection) url.openConnection(proxyConfig); if (RestClient.getUserAgent() != null) { conn.setRequestProperty("User-Agent", RestClient.getUserAgent()); } diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/LoginApi.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/LoginApi.java index f629aaf5b..bcb7108b0 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/LoginApi.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/LoginApi.java @@ -34,7 +34,7 @@ public interface LoginApi { /** - * Get the different login flows supported by the server. + * Get the supported versions of the homeserver */ @GET(RestClient.URI_API_PREFIX_PATH + "versions") Call versions(); diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/RoomsApi.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/RoomsApi.java index e513e08a7..9c50c0d68 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/RoomsApi.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/api/RoomsApi.java @@ -36,7 +36,6 @@ import org.matrix.androidsdk.rest.model.Typing; import org.matrix.androidsdk.rest.model.User; import org.matrix.androidsdk.rest.model.UserIdAndReason; -import org.matrix.androidsdk.rest.model.message.Message; import org.matrix.androidsdk.rest.model.sync.RoomResponse; import java.util.List; @@ -72,10 +71,10 @@ public interface RoomsApi { * * @param txId the transaction Id * @param roomId the room id - * @param message the message + * @param content the message */ @PUT("rooms/{roomId}/send/m.room.message/{txId}") - Call sendMessage(@Path("txId") String txId, @Path("roomId") String roomId, @Body Message message); + Call sendMessage(@Path("txId") String txId, @Path("roomId") String roomId, @Body JsonObject content); /** * Update the power levels diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/RoomsRestClient.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/RoomsRestClient.java index db5d78c8f..a10863150 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/RoomsRestClient.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/RoomsRestClient.java @@ -50,7 +50,6 @@ import org.matrix.androidsdk.rest.model.User; import org.matrix.androidsdk.rest.model.UserIdAndReason; import org.matrix.androidsdk.rest.model.filter.RoomEventFilter; -import org.matrix.androidsdk.rest.model.message.Message; import org.matrix.androidsdk.rest.model.sync.AccountDataElement; import org.matrix.androidsdk.rest.model.sync.RoomResponse; @@ -83,20 +82,23 @@ public RoomsRestClient(HomeServerConnectionConfig hsConfig) { * * @param transactionId the unique transaction id (it should avoid duplicated messages) * @param roomId the room id - * @param message the message + * @param content the message * @param callback the callback containing the created event if successful */ - public void sendMessage(final String transactionId, final String roomId, final Message message, final ApiCallback callback) { + public void sendMessage(final String transactionId, + final String roomId, + final JsonObject content, + final ApiCallback callback) { // privacy // final String description = "SendMessage : roomId " + roomId + " - message " + message.body; final String description = "SendMessage : roomId " + roomId; // the messages have their dedicated method in MXSession to be resent if there is no available network - mApi.sendMessage(transactionId, roomId, message) + mApi.sendMessage(transactionId, roomId, content) .enqueue(new RestAdapterCallback(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { - sendMessage(transactionId, roomId, message, callback); + sendMessage(transactionId, roomId, content, callback); } })); } diff --git a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/UrlPostTask.java b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/UrlPostTask.java index eff45ba40..f15c2a714 100644 --- a/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/UrlPostTask.java +++ b/matrix-sdk/src/main/java/org/matrix/androidsdk/rest/client/UrlPostTask.java @@ -18,6 +18,9 @@ import android.os.AsyncTask; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -29,6 +32,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; /** @@ -57,13 +61,25 @@ public interface IPostTaskListener { // the post listener private IPostTaskListener mListener; + // the proxy + @NonNull + private Proxy mProxy; + + public UrlPostTask(@Nullable Proxy proxy) { + if (proxy == null) { + mProxy = Proxy.NO_PROXY; + } else { + mProxy = proxy; + } + } + @Override protected String doInBackground(String... params) { String result = ""; try { URL url = new URL(params[0]); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(mProxy); if (RestClient.getUserAgent() != null) { conn.setRequestProperty("User-Agent", RestClient.getUserAgent()); } diff --git a/matrix-sdk/src/main/res/values-az/strings.xml b/matrix-sdk/src/main/res/values-az/strings.xml new file mode 100644 index 000000000..c4347619d --- /dev/null +++ b/matrix-sdk/src/main/res/values-az/strings.xml @@ -0,0 +1,189 @@ + + + %1$s: %2$s + %1$s şəkil göndərdi. + %1$s stiker göndərdi. + + %s-nin dəvəti + %1$s dəvət etdi %2$s + %1$s sizi dəvət etdi + %1$s qoşuldu + %1$s qalıb + %1$s dəvəti rədd etdi + %1$s %2$s-i xaric etdi + %1$s %2$s-i blokdan açdı + %1$s %2$s-i blokladı + %1$s %2$s-in dəvətini geri götürdü + %1$s avatarı dəyişdi + %1$s ekran adını %2$s olaraq təyin etdi + %1$s ekran adını %2$s-dan %3$s-ya dəyişdi + %1$s onların göstərilən adlarını sildi (%2$s) + %1$s mövzunu dəyişdi: %2$s + %1$s otaq adını dəyişdirdi: %2$s + %s video zəng etdi. + %s səsli zəng etdi. + %s zəngə cavab verdi. + %s zəng başa çatdı. + "%1$s gələcək otaq tarixçəsini %2$s-ə görünən etdi" + bütün otaq üzvləri, dəvət olunduğu andan. + bütün otaq üzvləri, qoşulduğu andan. + bütün otaq üzvləri. + hər kəs. + naməlum (%s). + %1$s sondan-sona şifrələmə açdı (%2$s) + %s bu otağı təkmilləşdirdi. + + %1$s VoIP konfrans istədi + VoIP konfransı başladı + VoIP konfransı başa çatdı + + (avatar da dəyişdirilib) + %1$s otaq adını sildi + %1$s otaq mövzusunu sildi + Mesaj silindi + Mesaj %1$s tərəfindən silindi + Mesaj silindi [səbəb: %1$s] + Mesaj %1$s tərəfindən qaldırıldı [səbəb: %2$s] + %1$s profilini %2$s yenilədi + %1$s otağa qoşulmaq üçün %2$s dəvətnamə göndərdi + %1$s otağa qoşulmaq üçün %2$s dəvətini ləğv etdi + %1$s %2$s üçün dəvəti qəbul etdi + + ** Şifrəni aça bilmir: %s ** + Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib. + + Cavab olaraq + + Redaktə etmək olmur + Mesaj göndərmək olmur + + Şəkil yükləmək olmur + + Şəbəkə xətası + Matris xətası + + Boş bir otağa yenidən qoşulmaq hazırda mümkün deyil. + + Şifrəli mesaj + + Elektron poçt ünvanı + Telefon nömrəsi + + şəkil göndərdi. + video göndərdi. + səs faylı göndərdi. + fayl göndərdi. + + %s-dən dəvət + Otağa dəvət + + %1$s və %2$s + + + %1$s və 1 digər + %1$s və %2$d digərləri + + + Boş otaq + + + It + Pişik + Aslan + At + Kərgədan + Donuz + Fil + Dovşan + Panda + Xoruz + Pinqvin + Tısbağa + Balıq + Ahtapot + Kəpənək + Çiçək + Ağac + Kaktus + Göbələk + Qlobus + Ay + Bulud + Atəş + Banan + Alma + Çiyələk + Qarğıdalı + Pizza + Tort + Ürək + Təbəssüm + Robot + Papaq + Eynəklər + Açar + Santa + Baş barmaqlar yuxarı + Çətir + Qum saatı + Saat + Hədiyyə + Lampa + Kitab + Qələm + Kağız sancağı + Qayçı + Qıfıl + Açar + Çəkic + Telefon + Bayraq + Qatar + Velosiped + Təyyarə + Raket + Kubok + Top + Gitara + Saz + Zəng + Anker + Qulaqlıqlar + Qovluq + Sancaq + + İlkin sinxronizasiya: +\nHesab idxal olunur… + İlkin sinxronizasiya: +\nKriptografiyanın idxalı + İlkin sinxronizasiya: +\nOtaqlar idxalı + İlkin sinxronizasiya: +\nOtaqlara daxil olmaq + İlkin sinxronizasiya: +\nDəvət olunmuş otaqların idxalı + İlkin sinxronizasiya: +\nTərk olunmuş otaqların idxalı + İlkin sinxronizasiya: +\nİcmaların idxalı + İlkin sinxronizasiya: +\nHesab məlumatlarının idxalı + + Mesaj göndərilir… + Göndərmə növbəsini təmizləyin + + %1$s-nin dəvəti. Səbəb: %2$s + %1$s dəvət olunmuş %2$s. Səbəb: %3$s + %1$s sizi dəvət etdi. Səbəb: %2$s + %1$s qoşuldu. Səbəb: %2$s + %1$s qalıb. Səbəb: %2$s + %1$s dəvəti rədd etdi. Səbəb: %2$s + %1$s %2$s-i xaric etdi. Səbəb: %3$s + %1$s blokdan açdı %2$s. Səbəb: %3$s + %1$s blokladı %2$s. Səbəb: %3$s + %1$s otağa qoşulmaq üçün %2$s dəvətnamə göndərdi. Səbəb: %3$s + %1$s otağa qoşulmaq üçün %2$s dəvətini ləğv etdi. Səbəb: %3$s + %1$s %2$s üçün dəvəti qəbul etdi. Səbəb: %3$s + %1$s %2$s dəvətini geri götürdü. Səbəb: %3$s + + diff --git a/matrix-sdk/src/main/res/values-bg/strings.xml b/matrix-sdk/src/main/res/values-bg/strings.xml index 2566ee780..2a8c087e2 100644 --- a/matrix-sdk/src/main/res/values-bg/strings.xml +++ b/matrix-sdk/src/main/res/values-bg/strings.xml @@ -173,4 +173,33 @@ Изчисти опашката за изпращане %1$s оттегли поканата за присъединяване на %2$s към стаята + поканата на %1$s. Причина: %2$s + %1$s покани %2$s. Причина: %3$s + %1$s ви покани. Причина: %2$s + %1$s се присъедини. Причина: %2$s + %1$s напусна. Причина: %2$s + %1$s отхвърли поканата. Причина: %2$s + %1$s изгони %2$s. Причина: %3$s + %1$s блокира %2$s. Причина: %3$s + %1$s блокира %2$s. Причина: %3$s + %1$s изпрати покана до %2$s да се присъедини в стаята. Причина: %3$s + %1$s премахна поканата за присъединяване на %2$s в стаята. Причина: %3$s + %1$s прие поканата за %2$s. Причина: %3$s + %1$s оттегли поканата на %2$s. Причина: %3$s + + + %1$s добави %2$s като адрес за тази стая. + %1$s добави %2$s като адреси за тази стая. + + + + %1$s премахна %2$s като адрес за тази стая. + %1$s премахна %2$s като адреси за тази стая. + + + %1$s добави %2$s и премахна %3$s като адреси за тази стая. + + %1$s настрой %2$s като основен адрес за тази стая. + %1$s премахна основния адрес за тази стая. + diff --git a/matrix-sdk/src/main/res/values-bn-rIN/strings.xml b/matrix-sdk/src/main/res/values-bn-rIN/strings.xml index 6f3dc28ff..5f21d3343 100644 --- a/matrix-sdk/src/main/res/values-bn-rIN/strings.xml +++ b/matrix-sdk/src/main/res/values-bn-rIN/strings.xml @@ -149,4 +149,27 @@ ফোল্ডার পিন + %s এই ঘরটিকে আপগ্রেড করেছে। + + %1$s %2$s এর কক্ষে যোগদানের আমন্ত্রণ বাতিল করে দিয়েছিল + প্রাথমিক সিঙ্ক: +\nঅ্যাকাউন্ট আমদানি করা হচ্ছে … + প্রাথমিক সিঙ্ক: +\nক্রিপ্টো আমদানি হচ্ছে + প্রাথমিক সিঙ্ক: +\nরুমগুলি আমদানি হচ্ছে + প্রাথমিক সিঙ্ক: +\nযোগকরা কক্ষগুলি আমদানি হচ্ছে + প্রাথমিক সিঙ্ক: +\nআমন্ত্রিত কক্ষগুলি আমদানি হচ্ছে + প্রাথমিক সিঙ্ক: +\nছেড়ে দেওয়া কক্ষগুলি আমদানি করা হচ্ছে + প্রাথমিক সিঙ্ক: +\nসম্প্রদায়গুলি আমদানি করা হচ্ছে + প্রাথমিক সিঙ্ক: +\nঅ্যাকাউন্ট ডেটা আমদানি করা হচ্ছে + + বার্তা প্রেরণ করা হচ্ছে … + প্রেরণ সারি পরিষ্কার করুন + diff --git a/matrix-sdk/src/main/res/values-cs/strings.xml b/matrix-sdk/src/main/res/values-cs/strings.xml index f6e23752c..61f3db0b2 100644 --- a/matrix-sdk/src/main/res/values-cs/strings.xml +++ b/matrix-sdk/src/main/res/values-cs/strings.xml @@ -82,4 +82,86 @@ Prázdná místnost + %s upravil/a tuto místnost. + + Zpráva byla smazána [důvod: %1$s] + Zpráva smazána [smazal/a %1$s] [důvod: %2$s] + "%1$s obnovil/a pozvánku do místnosti pro %2$s" + Kočka + Lev + Kůň + Jednorožec + Prase + Slon + Králík + Panda + Kohout + Tučnák + Želva + Ryba + Chobotnice + Motýl + Květina + Strom + Kaktus + Houba + Glóbus + Měsíc + Mrak + Oheň + Banán + Jablko + Jahoda + Kukuřice + Pizza + Dort + Srdce + Smajlík + Robot + Klobouk + Brýle + Santa + Zvednutý palec + Deštník + Přesípací hodiny + Hodiny + Dárek + Žárovka + Knížka + Tužka + Sponka + Nůžky + Zámek + Klíč + Kladivo + Telefon + Vlajka + Vlak + Kolo + Letadlo + Raketa + Pohár + Míč + Kytara + Trumpeta + Zvon + Kotva + Sluchátka + Složka + Úvodní synchronizace: +\nStahuji účet… + Uvodní synchronizace: +\nStahuji klíče + Uvodní synchnizace: +\nStahuji místnost + Uvodní synchronizace: +\nStahuji moje místnosti + Uvodní synchonizace: +\nStahuji místnosti, které jsem opustil/a + Úvodní sychronizace: +\nImportuji komunity + Úvodní synchronizace: +\nImportuji data účtu + + Posílám zprávu… diff --git a/matrix-sdk/src/main/res/values-de/strings.xml b/matrix-sdk/src/main/res/values-de/strings.xml index 59b5ee421..fa66f96ab 100644 --- a/matrix-sdk/src/main/res/values-de/strings.xml +++ b/matrix-sdk/src/main/res/values-de/strings.xml @@ -176,4 +176,18 @@ Erste Synchronisation: Importiere Benutzerdaten %1$s hat die Einladung an %2$s, den Raum zu betreten, zurückgezogen + %1$s\'s Einladung. Grund: %2$s + %1$s hat %2$s eingeladen. Grund: %3$s + %1$s hat dich eingeladen. Grund: %2$s + %1$s beigetreten. Grund: %2$s + %1$s ging. Grund: %2$s + %1$s hat die Einladung abgelehnt. Grund: %2$s + %1$s hat %2$s gekickt. Grund: %3$s + %1$s hat Verbannung für %2$s aufgehoben. Grund: %3$s + %1$s hat %2$s verbannt. Grund: %3$s + %1$s hat eine Einladung an %2$s gesandt um diesem Raum beizutreten. Grund: %3$s + %1$s hat Einladung an %2$s zu Betreten dieses Raumes zurückgezogen. Grund: %3$s + %1$s hat die Einladung für %2$s angenommen. Grund: %3$s + %1$s hat Einladung für %2$s verworfen. Grund: %3$s + diff --git a/matrix-sdk/src/main/res/values-es/strings.xml b/matrix-sdk/src/main/res/values-es/strings.xml index 0acb2bfc0..bcffeb0c8 100644 --- a/matrix-sdk/src/main/res/values-es/strings.xml +++ b/matrix-sdk/src/main/res/values-es/strings.xml @@ -96,4 +96,86 @@ Mensaje eliminado por %1$s Mensaje eliminado [motivo: %1$s] Mensaje eliminado por %1$s [motivo: %2$s] + %1$s ha revocado la invitación a unirse a la sala para %2$s + Perro + Gato + León + Caballo + Unicornio + Cerdo + Elefante + Conejo + Panda + Gallo + Pingüino + Tortuga + Pez + Pulpo + Mariposa + Flor + Árbol + Cactus + Seta + Luna + Nube + Fuego + Plátano + Manzana + Fresa + Maíz + Pizza + Pastel + Corazón + Sombrero + Gafas + Llave inglesa + Pulgares arriba + Paraguas + Reloj de arena + Reloj + Regalo + Bombilla + Libro + Lápiz + Clip + Tijeras + Candado + Llave + Martillo + Teléfono + Bandera + Tren + Bicicleta + Avión + Cohete + Trofeo + Pelota + Guitarra + Trompeta + Campana + Ancla + Auriculares + Carpeta + Sincronización Inicial +\nImportando cuenta… + Sincronización Inicial: +\nImportando Salas + Sincronización Inicial: +\nImportando Comunidades + Sincronización Inicial: +\nImportando Datos de la Cuenta + + Enviando mensaje… + Borrar cola de envío + + %1$s ha invitado a %2$s. Razón: %3$s + %1$s te ha invitado. Razón: %2$s + %1$s se ha unido. Razón: %2$s + %1$s se ha ido. Razón: %2$s + %1$s ha rechadazo la invitación. Razón: %2$s + %1$s expulsó a %2$s. Razón: %3$s + %1$s ha baneado a %2$s. Razón: %3$s + %1$s ha aceptado la invitación para %2$s. Razón: %3$s + %1$s ha eliminado la dirección principal para esta sala. + diff --git a/matrix-sdk/src/main/res/values-eu/strings.xml b/matrix-sdk/src/main/res/values-eu/strings.xml index 5b3685825..08b9b2833 100644 --- a/matrix-sdk/src/main/res/values-eu/strings.xml +++ b/matrix-sdk/src/main/res/values-eu/strings.xml @@ -173,4 +173,33 @@ Garbitu bidalketa-ilara %1$s erabiltzaileak %2$s gelara elkartzeko gonbidapena indargabetu du + %1$s erabiltzailearen gonbidapena. Arrazoia: %2$s + %1$s erabiltzaileak %2$s gonbidatu du. Arrazoia: %3$s + %1$s erabiltzaileak gonbidatu zaitu. Arrazoia: %2$s + %1$s elkartu da. Arrazoia: %2$s + %1$s atera da. Arrazoia: %2$s + %1$s erabiltzaileak gonbidapena baztertu du. Arrazoia: %2$s + %1$s erabiltzaileak %2$s kanporatu du. Arrazoia: %3$s + %1$s erabiltzaileak debekua kendu dio %2$s erabiltzaileari. Arrazoia: %3$s + %1$s erabiltzaileak %2$s debekatu du. Arrazoia: %3$s + "%1$s erabiltzaileak gelara elkartzeko gonbidapen bat bidali dio %2$s erabiltzaileari. Arrazoia: %3$s" + "%1$s erabiltzaileak %2$s gelara elkartzeko gonbidapena indargabetu du. Arrazoia: %3$s" + "%1$s erabiltzaileak %2$s gelarako gonbidapena onartu du. Arrazoia: %3$s" + "%1$s erabiltzaileak %2$s erabiltzailearen gonbidapena indargabetu du. Arrazoia: %3$s" + + + %1$s erabiltzaileak %2$s gehitu du gela honen helbide gisa. + %1$s erabiltzaileak %2$s gehitu ditu gela honen helbide gisa. + + + + %1$s erabiltzaileak %2$s kendu du gela honen helbide gisa. + %1$s erabiltzaileak %3$s kendu ditu gela honen helbide gisa. + + + %1$s erabiltzaileak %2$s gehitu %3$s eta kendu ditu gela honen helbide gisa. + + %1$s erabiltzaileak %2$s ezarri du gela honen helbide nagusi gisa. + %1$s erabiltzaileak gela honen helbide nagusia kendu du. + diff --git a/matrix-sdk/src/main/res/values-fi/strings.xml b/matrix-sdk/src/main/res/values-fi/strings.xml index 3c02e5c2c..b101b9173 100644 --- a/matrix-sdk/src/main/res/values-fi/strings.xml +++ b/matrix-sdk/src/main/res/values-fi/strings.xml @@ -174,4 +174,18 @@ Tyhjennä lähetysjono %1$s veti takaisin käyttäjän %2$s liittymiskutsun huoneeseen + Henkilön %1$s kutsu. Syy: %2$s + %1$s kutsui henkilön %2$s. Syy: %3$s + %1$s kutsui sinut. Syy: %2$s + %1$s liittyi. Syy: %2$s + %1$s poistui. Syy: %2$s + %1$s hylkäsi kutsun. Syy: %2$s + %1$s potkaisi käyttäjän %2$s pois. Syy: %3$s + %1$s poisti eston käyttäjältä %2$s. Syy: %3$s + %1$s esti käyttäjän %2$s. Syy: %3$s + %1$s lähetti kutsun käyttäjälle %2$s huoneeseen liittymiseksi. Syy: %3$s + %1$s kumosi kutsun käyttäjälle %2$s huoneeseen liittymiseksi. Syy: %3$s + %1$s hyväksyi kutsun liityäkseen huoneeseen %2$s. Syy: %3$s + %1$s poisti käyttäjän %2$s kutsun. Syy: %3$s + diff --git a/matrix-sdk/src/main/res/values-fr/strings.xml b/matrix-sdk/src/main/res/values-fr/strings.xml index 98a98a3e7..73341fe57 100644 --- a/matrix-sdk/src/main/res/values-fr/strings.xml +++ b/matrix-sdk/src/main/res/values-fr/strings.xml @@ -173,4 +173,33 @@ Vider la file d’envoi %1$s a révoqué l’invitation pour %2$s à rejoindre le salon + Invitation de %1$s. Raison : %2$s + %1$s a invité %2$s. Raison : %3$s + %1$s vous a invité. Raison : %2$s + %1$s a rejoint le salon. Raison : %2$s + %1$s est parti. Raison : %2$s + %1$s a refusé l’invitation. Raison : %2$s + %1$s a expulsé %2$s. Raison : %3$s + %1$s a révoqué le bannissement de %2$s. Raison : %3$s + %1$s a banni %2$s. Raison : %3$s + %1$s a envoyé une invitation à %2$s pour rejoindre le salon. Raison : %3$s + %1$s a révoqué l’invitation de %2$s à rejoindre le salon. Raison : %3$s + %1$s a accepté l’invitation pour %2$s. Raison : %3$s + %1$s a annulé l’invitation de %2$s. Raison : %3$s + + + %1$s a ajouté %2$s comme adresse pour ce salon. + %1$s a ajouté %2$s comme adresses pour ce salon. + + + + %1$s a supprimé %2$s comme adresse pour ce salon. + %1$s a supprimé %3$s comme adresses pour ce salon. + + + %1$s a ajouté %2$s et supprimé %3$s comme adresses pour ce salon. + + %1$s a défini %2$s comme adresse principale pour ce salon. + %1$s a supprimé l’adresse principale de ce salon. + diff --git a/matrix-sdk/src/main/res/values-hu/strings.xml b/matrix-sdk/src/main/res/values-hu/strings.xml index da6b8f568..1367b8bf4 100644 --- a/matrix-sdk/src/main/res/values-hu/strings.xml +++ b/matrix-sdk/src/main/res/values-hu/strings.xml @@ -172,4 +172,33 @@ Küldő sor ürítése %1$s visszavonta a meghívót a belépéshez ebbe a szobába: %2$s + %1$s meghívója. Ok: %2$s + %1$s meghívta őt: %2$s. Ok: %3$s + %1$s meghívott. Ok: %2$s + %1$s csatlakozott. Ok: %2$s + %1$s kilépett. Ok: %2$s + %1$s visszautasította a meghívót. Ok: %2$s + %1$s kirúgta őt: %2$s. Ok: %3$s + %1$s visszaengedte őt: %2$s. Ok: %3$s + %1$s kitiltotta őt: %2$s. Ok: %3$s + %1$s meghívót küldött neki: %2$s, hogy lépjen be a szobába. Ok: %3$s + %1$s visszavonta %2$s meghívóját a szobába való belépéshez. Ok: %3$s + %1$s elfogadta a meghívót ide: %2$s. Ok: %3$s + %1$s visszavonta %2$s meghívóját. Ok: %3$s + + + %1$s ezt a címet adta a szobához: %2$s. + %1$s ezeket a címeket adta a szobához: %2$s. + + + + %1$s ezt a címet törölte a szobából: %3$s. + %1$s ezeket a címeket törölte a szobából: %3$s. + + + %1$s a szobához adta ezeket:%2$s és törölte ezeket: %3$s. + + %1$s a szoba elsődleges címét erre állította be: %2$s. + %1$s eltávolította a szoba elsődleges címét. + diff --git a/matrix-sdk/src/main/res/values-it/strings.xml b/matrix-sdk/src/main/res/values-it/strings.xml index a8d844ddd..2d726272c 100644 --- a/matrix-sdk/src/main/res/values-it/strings.xml +++ b/matrix-sdk/src/main/res/values-it/strings.xml @@ -173,4 +173,33 @@ Cancella la coda di invio %1$s ha revocato l\'invito a %2$s di unirsi alla stanza + Invito di %1$s. Motivo: %2$s + %1$s ha invitato %2$s. Motivo: %3$s + %1$s ti ha invitato. Motivo: %2$s + %1$s è entrato. Motivo: %2$s + %1$s è uscito. Motivo: %2$s + %1$s ha rifiutato l\'invito. Motivo: %2$s + %1$s ha buttato fuori %2$s. Motivo: %3$s + %1$s ha riammesso %2$s. Motivo: %3$s + %1$s ha bandito %2$s. Motivo: %3$s + %1$s ha inviato un invito a %2$s di unirsi alla stanza. Motivo: %3$s + %1$s ha revocato l\'invito a %2$s di unirsi alla stanza. Motivo: %3$s + %1$s ha accettato l\'invito per %2$s. Motivo: %3$s + %1$s ha rifiutato l\'invito di %2$s. Motivo: %3$s + + + %1$s ha aggiunto %2$s come indirizzo per questa stanza. + %1$s ha aggiunto %2$s come indirizzi per questa stanza. + + + + %1$s ha rimosso %2$s come indirizzo per questa stanza. + %1$s ha rimosso %3$s come indirizzi per questa stanza. + + + %1$s ha aggiunto %2$s e rimosso %3$s come indirizzi per questa stanza. + + %1$s ha impostato l\'indirizzo principale per questa stanza a %2$s. + %1$s ha rimosso l\'indirizzo principale per questa stanza. + diff --git a/matrix-sdk/src/main/res/values-ja/strings.xml b/matrix-sdk/src/main/res/values-ja/strings.xml index d2a051cae..b72d1a13c 100644 --- a/matrix-sdk/src/main/res/values-ja/strings.xml +++ b/matrix-sdk/src/main/res/values-ja/strings.xml @@ -40,7 +40,7 @@ 部屋のメンバー全員。 誰でも。 不明 (%s)。 - %1$sはエンドツーエンドの暗号化を有効にしました (%2$s) + %1$s がエンドツーエンド暗号化を有効にしました (%2$s) %1$s がVoIP会議をリクエストしました VoIP会議が開始されました diff --git a/matrix-sdk/src/main/res/values-ko/strings.xml b/matrix-sdk/src/main/res/values-ko/strings.xml index 9dfbb6609..68e94bb64 100644 --- a/matrix-sdk/src/main/res/values-ko/strings.xml +++ b/matrix-sdk/src/main/res/values-ko/strings.xml @@ -49,7 +49,7 @@ %1$s님이 %2$s님에게 방 초대를 보냈습니다 %1$s님이 %2$s의 초대를 수락했습니다 - ** 암호를 해독할 수 없음: %s ** + ** 암호를 복호화할 수 없음: %s ** 발신인의 기기에서 이 메시지의 키를 보내지 않았습니다. 관련 대화 diff --git a/matrix-sdk/src/main/res/values-nl/strings.xml b/matrix-sdk/src/main/res/values-nl/strings.xml index 6658f2158..179ee8969 100644 --- a/matrix-sdk/src/main/res/values-nl/strings.xml +++ b/matrix-sdk/src/main/res/values-nl/strings.xml @@ -140,7 +140,7 @@ Potlood Paperclip Schaar - Hangslot + Slot Sleutel Hamer Telefoon diff --git a/matrix-sdk/src/main/res/values-ru/strings.xml b/matrix-sdk/src/main/res/values-ru/strings.xml index 07411c097..b0f2a60ac 100644 --- a/matrix-sdk/src/main/res/values-ru/strings.xml +++ b/matrix-sdk/src/main/res/values-ru/strings.xml @@ -132,7 +132,7 @@ Робот Шляпа Очки - гаечный ключ + Гаечный ключ Санта Большой палец вверх Зонтик @@ -186,4 +186,18 @@ Очистить очередь отправки %1$s отозвал приглашение %2$s присоединиться к комнате + Приглашение %1$s. Причина: %2$s + %1$s приглашен %2$s. Причина: %3$s + %1$s пригласил вас. Причина: %2$s + %1$s присоединился. Причина: %2$s + Осталось %1$s. Причина: %2$s + %1$s отклонил приглашение. Причина: %2$s + %1$s выгнали %2$s. Причина: %3$s + %1$s разблокировано %2$s. Причина: %3$s + %1$s забанен %2$s. Причина: %3$s + %1$s отправил приглашение %2$s в комнату. Причина: %3$s + %1$s отозвал приглашение %2$s присоединиться к комнате. Причина: %3$s + %1$s принял приглашение для %2$s. Причина: %3$s + %1$s отозвал приглашение %2$s. Причина: %3$s + diff --git a/matrix-sdk/src/main/res/values-sk/strings.xml b/matrix-sdk/src/main/res/values-sk/strings.xml index d5c7d95fd..b729932b1 100644 --- a/matrix-sdk/src/main/res/values-sk/strings.xml +++ b/matrix-sdk/src/main/res/values-sk/strings.xml @@ -88,70 +88,70 @@ Správa odstránená používateľom %1$s Správa odstránená [dôvod: %1$s] Správa odstránená používateľom %1$s [dôvod: %2$s] - Pes - Mačka - Lev + Hlava psa + Hlava mačky + Hlava leva Kôň - Jednorožec - Prasa + Hlava jednorožca + Hlava prasaťa Slon - Zajac - Panda + Hlava zajaca + Hlava pandy Kohút Tučniak Korytnačka Ryba Chobotnica Motýľ - Kvetina - Strom + Tulipán + Listnatý strom Kaktus - Hríb + Huba Zemeguľa - Mesiac + Polmesiac Oblak Oheň Banán - Jablko + Červené jablko Jahoda - Kukurica + Kukuričný klas Pizza - Koláč - Srdce - Úsmev + Narodeninová torta + Červené + Škeriaca sa tvár Robot - Klobúk + Cylinder Okuliare - Skrutkovač - Mikuláš + Francúzsky kľúč + Santa Claus Palec nahor Dáždnik Presýpacie hodiny - Hodiny - Darček + Budík + Zabalený darček Žiarovka - Kniha + Zatvorená kniha Ceruzka - Kancelárska sponka + Sponka na papier Nožnice - Zámok + Zatvorená zámka Kľúč Kladivo Telefón - Vlajka - Vlak + Kockovaná zástava + Rušeň Bicykel Lietadlo Raketa Trofej - Lopta + Futbal Gitara Trúbka - Zvonček + Zvon Kotva - Schlúchadlá - Priečinok - Pin + Slúchadlá + Fascikel + Špendlík Úvodná synchronizácia: \nPrebieha import účtu… @@ -173,4 +173,5 @@ Odosielanie správy… Vymazať správy na odoslanie + %1$s zamietol pozvanie používateľa %2$s vstúpiť do miestnosti diff --git a/matrix-sdk/src/main/res/values-sq/strings.xml b/matrix-sdk/src/main/res/values-sq/strings.xml index cffd55a7f..d4a28e28d 100644 --- a/matrix-sdk/src/main/res/values-sq/strings.xml +++ b/matrix-sdk/src/main/res/values-sq/strings.xml @@ -169,4 +169,33 @@ Spastro radhë pritjeje %1$s shfuqizoi ftesën për %2$s për pjesëmarrje te dhoma + Ftesë e %1$s. Arsye: %2$s + %1$s ftoi %2$s. Arsye: %3$s + %1$s ju ftoi. Arsye: %2$s + %1$s erdhi. Arsye: %2$s + %1$s iku. Arsye: %2$s + %1$s hodhi poshtë ftesën. Arsye: %2$s + %1$s përzuri %2$s. Arsye: %3$s + %1$s hoqi dëbimin për %2$s. Arsye: %3$s + %1$s dëboi %2$s. Arsye: %3$s + %1$s dërgoi një ftesë për %2$s për të ardhur në dhomë. Arsye: %3$s + %1$s shfuqizoi ftesën për %2$s për të ardhur në dhomë. Arsye: %3$s + %1$s pranoi ftesën për %2$s. Arsye: %3$s + %1$s tërhoqi mbrapsht ftesën për %2$s. Arsye: %3$s + + + %1$s shtoi %2$s si një adresë për këtë dhomë. + %1$s shtoi %2$s si adresa për këtë dhomë. + + + + %1$s hoqi %2$s si adresë për këtë dhomë. + %1$s hoqi %3$s si adresa për këtë dhomë. + + + %1$s shtoi %2$s dhe hoqi %3$s si adresa për këtë dhomë. + + %1$s caktoi %2$s si adresë kryesore për këtë dhomë. + %1$s hoqi adresën kryesore për këtë dhomë. + diff --git a/matrix-sdk/src/main/res/values-uk/strings.xml b/matrix-sdk/src/main/res/values-uk/strings.xml index 9f8e9078a..bf83e39d7 100644 --- a/matrix-sdk/src/main/res/values-uk/strings.xml +++ b/matrix-sdk/src/main/res/values-uk/strings.xml @@ -32,7 +32,7 @@ %1$s змінив(ла) назву кімнати на: %2$s %s розпочав(ла) відеодзвінок. %s розпочав(ла) голосовий дзвінок. - %s віпдовів(ла) на дзвінок. + %s відповів(ла) на дзвінок. %s завершив(ла) дзвінок. %1$s зробив(ла) майбутню історію кімнати видимою для %2$s усіх співрозмовників, з моменту їх запрошення. @@ -80,7 +80,12 @@ %1$s та 1 інший %1$s та %2$d інші %1$s та %2$d інших - + + %s вдосконалили цю кімнату. + + Повідомлення видалено + %1$s видалили повідомлення + Повідомлення видалено [причина: %1$s] diff --git a/matrix-sdk/src/main/res/values-zh-rCN/strings.xml b/matrix-sdk/src/main/res/values-zh-rCN/strings.xml index 3aed8858a..6e3ced304 100644 --- a/matrix-sdk/src/main/res/values-zh-rCN/strings.xml +++ b/matrix-sdk/src/main/res/values-zh-rCN/strings.xml @@ -13,7 +13,7 @@ %1$s 封禁了 %2$s %1$s 更换了他们的头像 %1$s 将他们的昵称设置为 %2$s - %1$s 把他们的昵称从 %2$s 改为 %3$s + %1$s 把他的昵称从 %2$s 改为 %3$s %1$s 移除了他们的昵称 (%2$s) %1$s 把主题改为: %2$s %1$s 把聊天室名称改为: %2$s @@ -167,4 +167,7 @@ 正在发送消息… 清除正在发送队列 + %1$s 撤回了对 %2$s 邀请 + 置顶 + diff --git a/matrix-sdk/src/main/res/values-zh-rTW/strings.xml b/matrix-sdk/src/main/res/values-zh-rTW/strings.xml index 5b5ae3beb..7182b0708 100644 --- a/matrix-sdk/src/main/res/values-zh-rTW/strings.xml +++ b/matrix-sdk/src/main/res/values-zh-rTW/strings.xml @@ -171,4 +171,31 @@ 清除傳送佇列 %1$s 撤銷了 %2$s 加入聊天室的邀請 + %1$s 的邀請。理由:%2$s + %1$s 邀請了 %2$s。理由:%3$s + %1$s 邀請了您。理由:%2$s + %1$s 已加入。理由:%2$s + %1$s 已離開。理由:%2$s + %1$s 已回絕邀請。理由:%2$s + %1$s 踢走了 %2$s。理由:%3$s + %1$s 取消封鎖了 %2$s。理由:%3$s + %1$s 封鎖了 %2$s。理由:%3$s + %1$s 已傳送邀請給 %2$s 來加入聊天室。理由:%3$s + %1$s 撤銷了 %2$s 加入聊天室的邀請。理由:%3$s + %1$s 接受 %2$s 的邀請。理由:%3$s + %1$s 撤回了對 %2$s 的邀請。理由:%3$s + + + %1$s 新增了 %2$s 為此聊天室的地址。 + + + + %1$s 移除了此聊天室的 %3$s 地址。 + + + %1$s 為此聊天室新增 %2$s 並移除 %3$s 地址。 + + %1$s 為此聊天室設定了 %2$s 為主地址。 + %1$s 為此聊天室移除了主要地址。 + diff --git a/matrix-sdk/src/main/res/values/strings.xml b/matrix-sdk/src/main/res/values/strings.xml index ce26c2213..de70b2169 100644 --- a/matrix-sdk/src/main/res/values/strings.xml +++ b/matrix-sdk/src/main/res/values/strings.xml @@ -243,4 +243,41 @@ Sending message… Clear sending queue + %1$s\'s invitation. Reason: %2$s + %1$s invited %2$s. Reason: %3$s + %1$s invited you. Reason: %2$s + %1$s joined. Reason: %2$s + %1$s left. Reason: %2$s + %1$s rejected the invitation. Reason: %2$s + %1$s kicked %2$s. Reason: %3$s + %1$s unbanned %2$s. Reason: %3$s + %1$s banned %2$s. Reason: %3$s + %1$s sent an invitation to %2$s to join the room. Reason: %3$s + %1$s revoked the invitation for %2$s to join the room. Reason: %3$s + %1$s accepted the invitation for %2$s. Reason: %3$s + %1$s withdrew %2$s\'s invitation. Reason: %3$s + + + %1$s added %2$s as an address for this room. + %1$s added %2$s as addresses for this room. + + + + %1$s removed %2$s as an address for this room. + %1$s removed %3$s as addresses for this room. + + + %1$s added %2$s and removed %3$s as addresses for this room. + + "%1$s set the main address for this room to %2$s." + "%1$s removed the main address for this room." + + "%1$s has allowed guests to join the room." + "%1$s has prevented guests from joining the room." + + %1$s turned on end-to-end encryption. + %1$s turned on end-to-end encryption (unrecognised algorithm %2$s). + + %s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys. +