diff --git a/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json index 34250b1ae..7e2c62963 100644 --- a/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json +++ b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json @@ -2,17 +2,17 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "c81b4edb8abdd29b77836d16a7d991c2", + "identityHash": "c7b1b47572d7ace1b422d0a3887a54e1", "entities": [ { "tableName": "conversations", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user` INTEGER, `conversation_id` TEXT, `token` TEXT, `name` TEXT, `display_name` TEXT, `type` INTEGER, `count` INTEGER NOT NULL, `number_of_guests` INTEGER NOT NULL, `participants_count` INTEGER NOT NULL, `participant_type` INTEGER, `has_password` INTEGER NOT NULL, `session_id` TEXT, `favorite` INTEGER NOT NULL, `last_activity` INTEGER NOT NULL, `unread_messages` INTEGER NOT NULL, `unread_mention` INTEGER NOT NULL, `last_message` TEXT, `object_type` TEXT, `notification_level` INTEGER, `read_only_state` INTEGER, `lobby_state` INTEGER, `lobby_timer` INTEGER, `last_read_message` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, FOREIGN KEY(`user`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user` INTEGER, `conversation_id` TEXT, `token` TEXT, `name` TEXT, `display_name` TEXT, `type` INTEGER, `count` INTEGER NOT NULL, `number_of_guests` INTEGER NOT NULL, `participants_count` INTEGER NOT NULL, `participant_type` INTEGER, `has_password` INTEGER NOT NULL, `session_id` TEXT, `favorite` INTEGER NOT NULL, `last_activity` INTEGER NOT NULL, `unread_messages` INTEGER NOT NULL, `unread_mention` INTEGER NOT NULL, `last_message` TEXT, `object_type` TEXT, `notification_level` INTEGER, `read_only_state` INTEGER, `lobby_state` INTEGER, `lobby_timer` INTEGER, `last_read_message` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`user`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", "fields": [ { "fieldPath": "id", "columnName": "id", - "affinity": "INTEGER", - "notNull": false + "affinity": "TEXT", + "notNull": true }, { "fieldPath": "user", @@ -169,7 +169,7 @@ "columnNames": [ "id" ], - "autoGenerate": true + "autoGenerate": false }, "indices": [ { @@ -198,19 +198,19 @@ }, { "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `conversation` INTEGER, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, FOREIGN KEY(`conversation`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", "fields": [ { "fieldPath": "id", "columnName": "id", - "affinity": "INTEGER", - "notNull": false + "affinity": "TEXT", + "notNull": true }, { "fieldPath": "conversation", "columnName": "conversation", - "affinity": "INTEGER", - "notNull": false + "affinity": "TEXT", + "notNull": true }, { "fieldPath": "messageId", @@ -259,7 +259,7 @@ "columnNames": [ "id" ], - "autoGenerate": true + "autoGenerate": false }, "indices": [ { @@ -369,7 +369,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c81b4edb8abdd29b77836d16a7d991c2')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c7b1b47572d7ace1b422d0a3887a54e1')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt b/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt index d9c90b254..4e600c973 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt @@ -118,6 +118,7 @@ import org.apache.commons.lang3.StringEscapeUtils import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import org.koin.android.ext.android.inject import org.parceler.Parcel import org.webrtc.AudioSource import org.webrtc.AudioTrack @@ -194,10 +195,8 @@ class CallController(args: Bundle) : BaseController() { @JvmField @Inject var userUtils: UserUtils? = null - @JvmField - @Inject - var cookieManager: CookieManager? = null + val cookieManager: CookieManager by inject() private var peerConnectionFactory: PeerConnectionFactory? = null private var audioConstraints: MediaConstraints? = null private var videoConstraints: MediaConstraints? = null @@ -214,7 +213,7 @@ class CallController(args: Bundle) : BaseController() { private var pingDisposable: Disposable? = null private var iceServers: MutableList? = null private var cameraEnumerator: CameraEnumerator? = null - private var roomToken: String? = null + private var roomToken: String private val conversationUser: UserNgEntity? private var callSession: String? = null private var localMediaStream: MediaStream? = null @@ -416,7 +415,7 @@ class CallController(args: Bundle) : BaseController() { override fun onNext(roomsOverall: RoomsOverall) { for (conversation in roomsOverall.ocs.data) { if (roomId == conversation.conversationId) { - roomToken = conversation.token + roomToken = conversation.token.toString() break } } @@ -1321,9 +1320,11 @@ class CallController(args: Bundle) : BaseController() { Integer.valueOf(webSocketCommunicationEvent.hashMap!!["jobId"]!!) ) as NCSignalingMessage ) - "peerReadyForRequestingOffer" -> webSocketClient!!.requestOfferForSessionIdWithType( - webSocketCommunicationEvent.hashMap!!["sessionId"], "video" - ) + "peerReadyForRequestingOffer" -> webSocketCommunicationEvent.hashMap!!["sessionId"]?.let { + webSocketClient!!.requestOfferForSessionIdWithType( + it, "video" + ) + } } } @@ -2166,7 +2167,7 @@ class CallController(args: Bundle) : BaseController() { var sessionOrUserId = sessionOrUserId if (isFromAnEvent && hasExternalSignalingServer) { // get session based on userId - sessionOrUserId = webSocketClient!!.getSessionForUserId(sessionOrUserId) + sessionOrUserId = webSocketClient!!.getSessionForUserId(sessionOrUserId).toString() } sessionOrUserId += "+$type" diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 6b68dbf89..accddef5a 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -852,8 +852,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter } if (magicWebSocketInstance != null) { - magicWebSocketInstance?.joinRoomWithRoomTokenAndSession( - roomToken, currentConversation?.sessionId + magicWebSocketInstance?.joinRoomWithRoomTokenAndSession(roomToken!!, + currentConversation?.sessionId ) } if (startCallFromNotification != null && startCallFromNotification == true) { @@ -874,7 +874,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter inConversation = true if (magicWebSocketInstance != null) { magicWebSocketInstance?.joinRoomWithRoomTokenAndSession( - roomToken, + roomToken!!, currentConversation?.sessionId ) } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt index 66dfdc542..327bc41a3 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt @@ -61,7 +61,6 @@ import com.nextcloud.talk.events.EventStatus import com.nextcloud.talk.interfaces.ConversationInfoInterface import com.nextcloud.talk.jobs.DeleteConversationWorker import com.nextcloud.talk.jobs.LeaveConversationWorker -import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.GROUP_CONVERSATION @@ -75,8 +74,6 @@ import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.ParticipantsOverall import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.getCredentials -import com.nextcloud.talk.newarch.local.models.hasSpreedFeatureCapability -import com.nextcloud.talk.newarch.utils.getCredentials import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DisplayUtils diff --git a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java deleted file mode 100644 index b5f4a0184..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java +++ /dev/null @@ -1,486 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.controllers; - -import android.annotation.SuppressLint; -import android.content.pm.ActivityInfo; -import android.graphics.Bitmap; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.Build; -import android.os.Bundle; -import android.security.KeyChain; -import android.security.KeyChainException; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.ClientCertRequest; -import android.webkit.CookieSyncManager; -import android.webkit.SslErrorHandler; -import android.webkit.WebResourceRequest; -import android.webkit.WebResourceResponse; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ProgressBar; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; -import autodagger.AutoInjector; -import butterknife.BindView; -import com.bluelinelabs.conductor.RouterTransaction; -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.controllers.base.BaseController; -import com.nextcloud.talk.events.CertificateEvent; -import com.nextcloud.talk.jobs.PushRegistrationWorker; -import com.nextcloud.talk.models.LoginData; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.utils.bundle.BundleKeys; -import com.nextcloud.talk.utils.database.user.UserUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder; -import com.nextcloud.talk.utils.ssl.MagicTrustManager; -import com.uber.autodispose.AutoDispose; -import de.cotech.hw.fido.WebViewFidoBridge; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import io.requery.Persistable; -import io.requery.reactivex.ReactiveEntityStore; -import java.lang.reflect.Field; -import java.net.CookieManager; -import java.net.URLDecoder; -import java.security.PrivateKey; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import javax.inject.Inject; -import org.greenrobot.eventbus.EventBus; - -@AutoInjector(NextcloudTalkApplication.class) -public class WebViewLoginController extends BaseController { - - public static final String TAG = "WebViewLoginController"; - - private final String PROTOCOL_SUFFIX = "://"; - private final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"; - - @Inject - UserUtils userUtils; - @Inject - AppPreferences appPreferences; - @Inject - ReactiveEntityStore dataStore; - @Inject - MagicTrustManager magicTrustManager; - @Inject - EventBus eventBus; - @Inject - CookieManager cookieManager; - - @BindView(R.id.webview) - WebView webView; - - @BindView(R.id.progress_bar) - ProgressBar progressBar; - - private String assembledPrefix; - - private Disposable userQueryDisposable; - - private String baseUrl; - private boolean isPasswordUpdate; - - private String username; - private String password; - private int loginStep = 0; - - private boolean automatedLoginAttempted = false; - - private WebViewFidoBridge webViewFidoBridge; - - public WebViewLoginController() { - } - - public WebViewLoginController(String baseUrl, boolean isPasswordUpdate) { - this.baseUrl = baseUrl; - this.isPasswordUpdate = isPasswordUpdate; - } - - public WebViewLoginController(String baseUrl, boolean isPasswordUpdate, String username, - String password) { - this.baseUrl = baseUrl; - this.isPasswordUpdate = isPasswordUpdate; - this.username = username; - this.password = password; - } - - private String getWebLoginUserAgent() { - return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + - Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL + " (" - + getResources().getString(R.string.nc_app_name) + ")"; - } - - @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_web_view_login, container, false); - } - - @SuppressLint("SetJavaScriptEnabled") - @Override - protected void onViewBound(@NonNull View view) { - super.onViewBound(view); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - if (getActivity() != null) { - getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - - if (getActionBar() != null) { - getActionBar().hide(); - } - - assembledPrefix = - getResources().getString(R.string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/"; - - webView.getSettings().setAllowFileAccess(false); - webView.getSettings().setAllowFileAccessFromFileURLs(false); - webView.getSettings().setJavaScriptEnabled(true); - webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(false); - webView.getSettings().setDomStorageEnabled(true); - webView.getSettings().setUserAgentString(getWebLoginUserAgent()); - webView.getSettings().setSaveFormData(false); - webView.getSettings().setSavePassword(false); - webView.getSettings().setRenderPriority(WebSettings.RenderPriority.HIGH); - webView.clearCache(true); - webView.clearFormData(); - webView.clearHistory(); - WebView.clearClientCertPreferences(null); - - webViewFidoBridge = - WebViewFidoBridge.createInstanceForWebView((AppCompatActivity) getActivity(), webView); - - CookieSyncManager.createInstance(getActivity()); - android.webkit.CookieManager.getInstance().removeAllCookies(null); - - Map headers = new HashMap<>(); - headers.put("OCS-APIRequest", "true"); - - webView.setWebViewClient(new WebViewClient() { - private boolean basePageLoaded; - - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - webViewFidoBridge.delegateShouldInterceptRequest(view, request); - return super.shouldInterceptRequest(view, request); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); - webViewFidoBridge.delegateOnPageStarted(view, url, favicon); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.startsWith(assembledPrefix)) { - parseAndLoginFromWebView(url); - return true; - } - return false; - } - - @Override - public void onPageFinished(WebView view, String url) { - loginStep++; - - if (!basePageLoaded) { - if (progressBar != null) { - progressBar.setVisibility(View.GONE); - } - - if (webView != null) { - webView.setVisibility(View.VISIBLE); - } - basePageLoaded = true; - } - - if (!TextUtils.isEmpty(username) && webView != null) { - if (loginStep == 1) { - webView.loadUrl("javascript: {document.getElementsByClassName('login')[0].click(); };"); - } else if (!automatedLoginAttempted) { - automatedLoginAttempted = true; - if (TextUtils.isEmpty(password)) { - webView.loadUrl("javascript:var justStore = document.getElementById('user').value = '" - + username - + "';"); - } else { - webView.loadUrl("javascript: {" + - "document.getElementById('user').value = '" + username + "';" + - "document.getElementById('password').value = '" + password + "';" + - "document.getElementById('submit').click(); };"); - } - } - } - - super.onPageFinished(view, url); - } - - @Override - public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { - UserEntity userEntity = userUtils.getCurrentUser(); - - String alias = null; - if (!isPasswordUpdate) { - alias = appPreferences.getTemporaryClientCertAlias(); - } - - if (TextUtils.isEmpty(alias) && (userEntity != null)) { - alias = userEntity.getClientCertificate(); - } - - if (!TextUtils.isEmpty(alias)) { - String finalAlias = alias; - new Thread(() -> { - try { - PrivateKey privateKey = KeyChain.getPrivateKey(getActivity(), finalAlias); - X509Certificate[] certificates = - KeyChain.getCertificateChain(getActivity(), finalAlias); - if (privateKey != null && certificates != null) { - request.proceed(privateKey, certificates); - } else { - request.cancel(); - } - } catch (KeyChainException | InterruptedException e) { - request.cancel(); - } - }).start(); - } else { - KeyChain.choosePrivateKeyAlias(getActivity(), chosenAlias -> { - if (chosenAlias != null) { - appPreferences.setTemporaryClientCertAlias(chosenAlias); - new Thread(() -> { - PrivateKey privateKey = null; - try { - privateKey = KeyChain.getPrivateKey(getActivity(), chosenAlias); - X509Certificate[] certificates = - KeyChain.getCertificateChain(getActivity(), chosenAlias); - if (privateKey != null && certificates != null) { - request.proceed(privateKey, certificates); - } else { - request.cancel(); - } - } catch (KeyChainException | InterruptedException e) { - request.cancel(); - } - }).start(); - } else { - request.cancel(); - } - }, new String[] { "RSA", "EC" }, null, request.getHost(), request.getPort(), null); - } - } - - @Override - public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - try { - SslCertificate sslCertificate = error.getCertificate(); - Field f = sslCertificate.getClass().getDeclaredField("mX509Certificate"); - f.setAccessible(true); - X509Certificate cert = (X509Certificate) f.get(sslCertificate); - - if (cert == null) { - handler.cancel(); - } else { - try { - magicTrustManager.checkServerTrusted(new X509Certificate[] { cert }, "generic"); - handler.proceed(); - } catch (CertificateException exception) { - eventBus.post(new CertificateEvent(cert, magicTrustManager, handler)); - } - } - } catch (Exception exception) { - handler.cancel(); - } - } - - @Override - public void onReceivedError(WebView view, int errorCode, String description, - String failingUrl) { - super.onReceivedError(view, errorCode, description, failingUrl); - } - }); - - webView.loadUrl(baseUrl + "/index.php/login/flow", headers); - } - - private void dispose() { - if (userQueryDisposable != null && !userQueryDisposable.isDisposed()) { - userQueryDisposable.dispose(); - } - - userQueryDisposable = null; - } - - private void parseAndLoginFromWebView(String dataString) { - LoginData loginData = parseLoginData(assembledPrefix, dataString); - - if (loginData != null) { - dispose(); - - UserEntity currentUser = userUtils.getCurrentUser(); - - ApplicationWideMessageHolder.MessageType messageType = null; - - if (!isPasswordUpdate && userUtils.getIfUserWithUsernameAndServer(loginData.getUsername(), - baseUrl)) { - messageType = ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED; - } - - if (userUtils.checkIfUserIsScheduledForDeletion(loginData.getUsername(), baseUrl)) { - ApplicationWideMessageHolder.getInstance().setMessageType( - ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION); - - if (!isPasswordUpdate) { - getRouter().popToRoot(); - } else { - getRouter().popCurrentController(); - } - } - - ApplicationWideMessageHolder.MessageType finalMessageType = messageType; - cookieManager.getCookieStore().removeAll(); - - if (!isPasswordUpdate && finalMessageType == null) { - Bundle bundle = new Bundle(); - bundle.putString(BundleKeys.INSTANCE.getKEY_USERNAME(), loginData.getUsername()); - bundle.putString(BundleKeys.INSTANCE.getKEY_TOKEN(), loginData.getToken()); - bundle.putString(BundleKeys.INSTANCE.getKEY_BASE_URL(), loginData.getServerUrl()); - String protocol = ""; - - if (baseUrl.startsWith("http://")) { - protocol = "http://"; - } else if (baseUrl.startsWith("https://")) { - protocol = "https://"; - } - - if (!TextUtils.isEmpty(protocol)) { - bundle.putString(BundleKeys.INSTANCE.getKEY_ORIGINAL_PROTOCOL(), protocol); - } - - getRouter().pushController(RouterTransaction.with(new AccountVerificationController - (bundle)).pushChangeHandler(new HorizontalChangeHandler()) - .popChangeHandler(new HorizontalChangeHandler())); - } else { - if (isPasswordUpdate) { - if (currentUser != null) { - userQueryDisposable = userUtils.createOrUpdateUser(null, loginData.getToken(), - null, null, "", true, - null, currentUser.getId(), null, appPreferences.getTemporaryClientCertAlias(), null) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .as(AutoDispose.autoDisposable(getScopeProvider())) - .subscribe(userEntity -> { - if (finalMessageType != null) { - ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType); - } - - OneTimeWorkRequest pushRegistrationWork = - new OneTimeWorkRequest.Builder(PushRegistrationWorker.class).build(); - WorkManager.getInstance().enqueue(pushRegistrationWork); - - getRouter().popCurrentController(); - }, throwable -> dispose(), - this::dispose); - } - } else { - if (finalMessageType != null) { - ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType); - } - getRouter().popToRoot(); - } - } - } - } - - private LoginData parseLoginData(String prefix, String dataString) { - if (dataString.length() < prefix.length()) { - return null; - } - - LoginData loginData = new LoginData(); - - // format is xxx://login/server:xxx&user:xxx&password:xxx - String data = dataString.substring(prefix.length()); - - String[] values = data.split("&"); - - if (values.length != 3) { - return null; - } - - for (String value : values) { - if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { - loginData.setUsername(URLDecoder.decode( - value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()))); - } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { - loginData.setToken(URLDecoder.decode( - value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()))); - } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { - loginData.setServerUrl(URLDecoder.decode( - value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()))); - } else { - return null; - } - } - - if (!TextUtils.isEmpty(loginData.getServerUrl()) - && !TextUtils.isEmpty(loginData.getUsername()) - && - !TextUtils.isEmpty(loginData.getToken())) { - return loginData; - } else { - return null; - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - dispose(); - } - - @Override - protected void onDestroyView(@NonNull View view) { - super.onDestroyView(view); - if (getActivity() != null) { - getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.kt b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.kt new file mode 100644 index 000000000..3c7b644ee --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.kt @@ -0,0 +1,462 @@ +/* + * + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.controllers + +import android.annotation.SuppressLint +import android.content.pm.ActivityInfo +import android.net.http.SslError +import android.os.Build +import android.os.Bundle +import android.security.KeyChain +import android.security.KeyChainException +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.ClientCertRequest +import android.webkit.CookieSyncManager +import android.webkit.SslErrorHandler +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebSettings.RenderPriority.HIGH +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.appcompat.app.AppCompatActivity +import androidx.work.OneTimeWorkRequest.Builder +import androidx.work.WorkManager +import com.bluelinelabs.conductor.RouterTransaction +import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler +import com.nextcloud.talk.R.layout +import com.nextcloud.talk.R.string +import com.nextcloud.talk.controllers.base.BaseController +import com.nextcloud.talk.events.CertificateEvent +import com.nextcloud.talk.jobs.PushRegistrationWorker +import com.nextcloud.talk.models.LoginData +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.local.models.other.UserStatus +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME +import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder +import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder.MessageType +import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION +import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED +import com.nextcloud.talk.utils.ssl.MagicTrustManager +import de.cotech.hw.fido.WebViewFidoBridge +import kotlinx.android.synthetic.main.controller_web_view_login.view.progress_bar +import kotlinx.android.synthetic.main.controller_web_view_login.view.webview +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.android.ext.android.inject +import java.net.CookieManager +import java.net.URLDecoder +import java.security.PrivateKey +import java.security.cert.CertificateException +import java.security.cert.X509Certificate +import java.util.Locale + +class WebViewLoginController : BaseController { + private val PROTOCOL_SUFFIX = "://" + private val LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":" + + val magicTrustManager: MagicTrustManager by inject() + val cookieManager: CookieManager by inject() + val usersRepository: UsersRepository by inject() + + private var assembledPrefix: String? = null + private var baseUrl: String? = null + private var isPasswordUpdate = false + private var username: String? = null + private var password: String? = null + private var loginStep = 0 + private var automatedLoginAttempted = false + private var webViewFidoBridge: WebViewFidoBridge? = null + + constructor(bundle: Bundle) + constructor( + baseUrl: String?, + isPasswordUpdate: Boolean + ) { + this.baseUrl = baseUrl + this.isPasswordUpdate = isPasswordUpdate + } + + constructor( + baseUrl: String?, + isPasswordUpdate: Boolean, + username: String?, + password: String? + ) { + this.baseUrl = baseUrl + this.isPasswordUpdate = isPasswordUpdate + this.username = username + this.password = password + } + + private val webLoginUserAgent: String + private get() = (Build.MANUFACTURER.substring(0, 1).toUpperCase( + Locale.getDefault() + ) + + Build.MANUFACTURER.substring(1).toLowerCase( + Locale.getDefault() + ) + " " + Build.MODEL + " (" + + resources!!.getString(string.nc_app_name) + ")") + + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { + return inflater.inflate(layout.controller_web_view_login, container, false) + } + + @SuppressLint("SetJavaScriptEnabled") + override fun onViewBound(view: View) { + super.onViewBound(view) + if (activity != null) { + activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + if (actionBar != null) { + actionBar!!.hide() + } + assembledPrefix = + resources!!.getString(string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/" + + view.webview.apply { + settings.allowFileAccess = false + settings.allowFileAccessFromFileURLs = false + settings.javaScriptEnabled = true + settings.javaScriptCanOpenWindowsAutomatically = false + settings.domStorageEnabled = true + settings.userAgentString = webLoginUserAgent + settings.saveFormData = false + settings.savePassword = false + settings.setRenderPriority(HIGH) + clearCache(true) + clearFormData() + clearHistory() + clearSslPreferences() + } + + WebView.clearClientCertPreferences(null) + webViewFidoBridge = + WebViewFidoBridge.createInstanceForWebView(activity as AppCompatActivity?, view.webview) + CookieSyncManager.createInstance(activity) + android.webkit.CookieManager.getInstance() + .removeAllCookies(null) + val headers: MutableMap = hashMapOf() + headers["OCS-APIRequest"] = "true" + + view.webview.webViewClient = object : WebViewClient() { + private var basePageLoaded = false + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { + webViewFidoBridge?.delegateShouldInterceptRequest(view, request) + return super.shouldInterceptRequest(view, request) + } + + override fun shouldOverrideUrlLoading( + view: WebView, + url: String + ): Boolean { + if (url.startsWith(assembledPrefix!!)) { + parseAndLoginFromWebView(url) + return true + } + return false + } + + override fun onPageFinished( + view: WebView, + url: String + ) { + loginStep++ + if (!basePageLoaded) { + if (view.progress_bar != null) { + view.progress_bar!!.visibility = View.GONE + } + if (view.webview != null) { + view.webview.visibility = View.VISIBLE + } + basePageLoaded = true + } + if (!TextUtils.isEmpty(username)) { + if (loginStep == 1) { + view.webview.loadUrl( + "javascript: {document.getElementsByClassName('login')[0].click(); };" + ) + } else if (!automatedLoginAttempted) { + automatedLoginAttempted = true + if (TextUtils.isEmpty(password)) { + view.webview.loadUrl( + "javascript:var justStore = document.getElementById('user').value = '" + + username + + "';" + ) + } else { + view.webview.loadUrl( + "javascript: {" + + "document.getElementById('user').value = '" + username + "';" + + "document.getElementById('password').value = '" + password + "';" + + "document.getElementById('submit').click(); };" + ) + } + } + } + super.onPageFinished(view, url) + } + + override fun onReceivedClientCertRequest( + view: WebView, + request: ClientCertRequest + ) { + val userEntity = usersRepository.getActiveUser() + var alias: String? = null + if (!isPasswordUpdate) { + alias = appPreferences.temporaryClientCertAlias + } + if (TextUtils.isEmpty(alias)) { + alias = userEntity!!.clientCertificate + } + if (!TextUtils.isEmpty(alias)) { + val finalAlias = alias + Thread(Runnable { + try { + val privateKey = + KeyChain.getPrivateKey(activity!!, finalAlias!!) + val certificates = + KeyChain.getCertificateChain(activity!!, finalAlias) + if (privateKey != null && certificates != null) { + request.proceed(privateKey, certificates) + } else { + request.cancel() + } + } catch (e: KeyChainException) { + request.cancel() + } catch (e: InterruptedException) { + request.cancel() + } + }) + .start() + } else { + KeyChain.choosePrivateKeyAlias( + activity!!, { chosenAlias: String? -> + if (chosenAlias != null) { + appPreferences.temporaryClientCertAlias = chosenAlias + Thread(Runnable { + var privateKey: PrivateKey? = null + try { + privateKey = KeyChain.getPrivateKey(activity!!, chosenAlias) + val certificates = + KeyChain.getCertificateChain(activity!!, chosenAlias) + if (privateKey != null && certificates != null) { + request.proceed(privateKey, certificates) + } else { + request.cancel() + } + } catch (e: KeyChainException) { + request.cancel() + } catch (e: InterruptedException) { + request.cancel() + } + }) + .start() + } else { + request.cancel() + } + }, arrayOf("RSA", "EC"), null, request.host, request.port, null + ) + } + } + + override fun onReceivedSslError( + view: WebView, + handler: SslErrorHandler, + error: SslError + ) { + try { + val sslCertificate = error.certificate + val f = + sslCertificate.javaClass.getDeclaredField("mX509Certificate") + f.isAccessible = true + val cert = + f[sslCertificate] as X509Certificate + if (cert == null) { + handler.cancel() + } else { + try { + magicTrustManager.checkServerTrusted( + arrayOf(cert), "generic" + ) + handler.proceed() + } catch (exception: CertificateException) { + eventBus.post(CertificateEvent(cert, magicTrustManager, handler)) + } + } + } catch (exception: Exception) { + handler.cancel() + } + } + } + view.webview.loadUrl("$baseUrl/index.php/login/flow", headers) + } + + private fun parseAndLoginFromWebView(dataString: String) { + val loginData = parseLoginData(assembledPrefix, dataString) + if (loginData != null) { + GlobalScope.launch { + val targetUser = + usersRepository.getUserWithUsernameAndServer(loginData.username!!, baseUrl!!) + var messageType: MessageType? = null + + if (!isPasswordUpdate && targetUser != null) { + messageType = ACCOUNT_UPDATED_NOT_ADDED + } + + if (targetUser != null && UserStatus.PENDING_DELETE.equals(targetUser.status)) { + ApplicationWideMessageHolder.getInstance().messageType = ACCOUNT_SCHEDULED_FOR_DELETION + if (!isPasswordUpdate) { + withContext(Dispatchers.Main) { + router.popToRoot() + } + } else { + withContext(Dispatchers.Main) { + router.popCurrentController() + } + } + } + + val finalMessageType = messageType + cookieManager.cookieStore.removeAll() + + if (!isPasswordUpdate && finalMessageType == null) { + val bundle = Bundle() + bundle.putString(KEY_USERNAME, loginData.username) + bundle.putString(KEY_TOKEN, loginData.token) + bundle.putString(KEY_BASE_URL, loginData.serverUrl) + var protocol = "" + if (baseUrl!!.startsWith("http://")) { + protocol = "http://" + } else if (baseUrl!!.startsWith("https://")) { + protocol = "https://" + } + if (!TextUtils.isEmpty(protocol)) { + bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol) + } + + withContext(Dispatchers.Main) { + router.pushController( + RouterTransaction.with(AccountVerificationController(bundle)).pushChangeHandler( + HorizontalChangeHandler() + ) + .popChangeHandler(HorizontalChangeHandler()) + ) + } + } else { + if (isPasswordUpdate && targetUser != null) { + targetUser.token = loginData.token + val updatedRows = usersRepository.updateUser(targetUser) + if (updatedRows > 0) { + if (finalMessageType != null) { + ApplicationWideMessageHolder.getInstance().messageType = finalMessageType + } + + val pushRegistrationWork = Builder(PushRegistrationWorker::class.java).build() + WorkManager.getInstance() + .enqueue(pushRegistrationWork) + withContext(Dispatchers.Main) { + router.popCurrentController() + } + } else { + // do nothing + } + } else { + if (finalMessageType != null) { + ApplicationWideMessageHolder.getInstance() + .messageType = finalMessageType + } + withContext(Dispatchers.Main) { + router.popToRoot() + } + } + } + } + } + } + + private fun parseLoginData( + prefix: String?, + dataString: String + ): LoginData? { + if (dataString.length < prefix!!.length) { + return null + } + val loginData = LoginData() + // format is xxx://login/server:xxx&user:xxx&password:xxx + val data = dataString.substring(prefix.length) + val values = data.split("&") + .toTypedArray() + if (values.size != 3) { + return null + } + for (value in values) { + if (value.startsWith("user$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) { + loginData.username = URLDecoder.decode( + value.substring("user$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR".length) + ) + } else if (value.startsWith("password$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) { + loginData.token = URLDecoder.decode( + value.substring("password$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR".length) + ) + } else if (value.startsWith("server$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) { + loginData.serverUrl = URLDecoder.decode( + value.substring("server$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR".length) + ) + } else { + return null + } + } + return if (!TextUtils.isEmpty(loginData.serverUrl) + && !TextUtils.isEmpty(loginData.username) + && + !TextUtils.isEmpty(loginData.token) + ) { + loginData + } else { + null + } + } + + override fun onDestroyView(view: View) { + super.onDestroyView(view) + if (activity != null) { + activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + } + } + + companion object { + const val TAG = "WebViewLoginController" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/events/WebSocketCommunicationEvent.java b/app/src/main/java/com/nextcloud/talk/events/WebSocketCommunicationEvent.java index 738c91572..4405d3073 100644 --- a/app/src/main/java/com/nextcloud/talk/events/WebSocketCommunicationEvent.java +++ b/app/src/main/java/com/nextcloud/talk/events/WebSocketCommunicationEvent.java @@ -29,4 +29,10 @@ public class WebSocketCommunicationEvent { public final String type; @Nullable public final HashMap hashMap; + + public WebSocketCommunicationEvent(String type, + @Nullable HashMap hashMap) { + this.type = type; + this.hashMap = hashMap; + } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.java deleted file mode 100644 index 7913e87c2..000000000 --- a/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.jobs; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.Data; -import androidx.work.Worker; -import androidx.work.WorkerParameters; -import autodagger.AutoInjector; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.bundle.BundleKeys; -import com.nextcloud.talk.utils.database.user.UserUtils; -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.inject.Inject; - -@AutoInjector(NextcloudTalkApplication.class) -public class ShareOperationWorker extends Worker { - @Inject - UserUtils userUtils; - @Inject - NcApi ncApi; - private long userId; - private UserEntity operationsUser; - private String roomToken; - private List filesArray = new ArrayList<>(); - private String credentials; - private String baseUrl; - - public ShareOperationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - Data data = workerParams.getInputData(); - userId = data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), 0); - roomToken = data.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN()); - Collections.addAll(filesArray, data.getStringArray(BundleKeys.INSTANCE.getKEY_FILE_PATHS())); - operationsUser = userUtils.getUserWithId(userId); - credentials = ApiUtils.getCredentials(operationsUser.getUsername(), operationsUser.getToken()); - baseUrl = operationsUser.getBaseUrl(); - } - - @NonNull - @Override - public Result doWork() { - for (int i = 0; i < filesArray.size(); i++) { - ncApi.createRemoteShare(credentials, - ApiUtils.getSharingUrl(baseUrl), - filesArray.get(i), - roomToken, - "10") - .subscribeOn(Schedulers.io()) - .blockingSubscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onNext(Void aVoid) { - - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - - } - }); - } - - return Result.success(); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt new file mode 100644 index 000000000..464787537 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt @@ -0,0 +1,96 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.jobs + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import autodagger.AutoInjector +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.newarch.local.models.getCredentials +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_PATHS +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import io.reactivex.Observer +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import org.koin.core.KoinComponent +import org.koin.core.inject +import java.util.Collections +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class ShareOperationWorker( + context: Context, + workerParams: WorkerParameters +) : Worker(context, workerParams), KoinComponent { + @JvmField @Inject + var ncApi: NcApi? = null + + val usersRepository: UsersRepository by inject() + + private val userId: Long + private val operationsUser: UserNgEntity + private val roomToken: String? + private val filesArray = mutableListOf() + private val credentials: String + private val baseUrl: String + override fun doWork(): Result { + for (i in filesArray.indices) { + ncApi!!.createRemoteShare( + credentials, + ApiUtils.getSharingUrl(baseUrl), + filesArray[i], + roomToken, + "10" + ) + .subscribeOn(Schedulers.io()) + .blockingSubscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + override fun onError(e: Throwable) {} + override fun onComplete() {} + override fun onNext(t: Void) { + } + }) + } + return Result.success() + } + + init { + sharedApplication + ?.componentApplication + ?.inject(this) + val data = workerParams.inputData + userId = data.getLong(KEY_INTERNAL_USER_ID, 0) + roomToken = data.getString(KEY_ROOM_TOKEN) + + Collections.addAll( + filesArray, *data.getStringArray(KEY_FILE_PATHS) + ) + operationsUser = usersRepository.getUserWithId(userId) + credentials = operationsUser.getCredentials() + baseUrl = operationsUser.baseUrl + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/models/LoginData.java b/app/src/main/java/com/nextcloud/talk/models/LoginData.kt similarity index 73% rename from app/src/main/java/com/nextcloud/talk/models/LoginData.java rename to app/src/main/java/com/nextcloud/talk/models/LoginData.kt index ce8fa8b38..490fcb1b2 100644 --- a/app/src/main/java/com/nextcloud/talk/models/LoginData.java +++ b/app/src/main/java/com/nextcloud/talk/models/LoginData.kt @@ -18,15 +18,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.nextcloud.talk.models; +package com.nextcloud.talk.models -import lombok.Data; -import org.parceler.Parcel; +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import lombok.Data +import org.parceler.Parcel + +@Parcelize +data class LoginData ( + var serverUrl: String? = null, + var username: String? = null, + var token: String? = null +): Parcelable -@Parcel -@Data -public class LoginData { - String serverUrl; - String username; - String token; -} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java index 729679f3f..bb3d75440 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java @@ -65,9 +65,9 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent @JsonIgnore public Long internalUserId = null; @JsonIgnore - public Long internalMessageId = null; + public String internalMessageId = null; @JsonIgnore - public Long internalConversationId = null; + public String internalConversationId = null; @JsonField(name = "id") @Ignore public Long jsonMessageId; diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ActorWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ActorWebSocketMessage.java index a18220fda..d49ab461e 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ActorWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ActorWebSocketMessage.java @@ -30,11 +30,11 @@ import org.parceler.Parcel; @Parcel public class ActorWebSocketMessage { @JsonField(name = "type") - String type; + public String type; @JsonField(name = "sessionid") - String sessionId; + public String sessionId; @JsonField(name = "userid") - String userid; + public String userid; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java index 4cf15fe19..8f5b6363a 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java @@ -30,5 +30,5 @@ import org.parceler.Parcel; @Parcel public class BaseWebSocketMessage { @JsonField(name = "type") - String type; + public String type; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ByeWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ByeWebSocketMessage.java index f740dada1..e83df7d85 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ByeWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ByeWebSocketMessage.java @@ -31,5 +31,5 @@ import org.parceler.Parcel; @Data public class ByeWebSocketMessage extends BaseWebSocketMessage { @JsonField(name = "bye") - HashMap bye; + public HashMap bye; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallOverallWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallOverallWebSocketMessage.java index 45b1ebe13..1c066507a 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallOverallWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallOverallWebSocketMessage.java @@ -30,5 +30,5 @@ import org.parceler.Parcel; @Parcel public class CallOverallWebSocketMessage extends BaseWebSocketMessage { @JsonField(name = "message") - CallWebSocketMessage callWebSocketMessage; + public CallWebSocketMessage callWebSocketMessage; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallWebSocketMessage.java index 3cbe01abd..eedbf0c39 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/CallWebSocketMessage.java @@ -31,11 +31,11 @@ import org.parceler.Parcel; @Parcel public class CallWebSocketMessage { @JsonField(name = "recipient") - ActorWebSocketMessage recipientWebSocketMessage; + public ActorWebSocketMessage recipientWebSocketMessage; @JsonField(name = "sender") - ActorWebSocketMessage senderWebSocketMessage; + public ActorWebSocketMessage senderWebSocketMessage; @JsonField(name = "data") - NCSignalingMessage ncSignalingMessage; + public NCSignalingMessage ncSignalingMessage; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorOverallWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorOverallWebSocketMessage.java index dc1b56ee1..9f2dd686a 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorOverallWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorOverallWebSocketMessage.java @@ -30,5 +30,5 @@ import org.parceler.Parcel; @JsonObject public class ErrorOverallWebSocketMessage extends BaseWebSocketMessage { @JsonField(name = "error") - ErrorWebSocketMessage errorWebSocketMessage; + public ErrorWebSocketMessage errorWebSocketMessage; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorWebSocketMessage.java index e7ca6d98f..d227980b4 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ErrorWebSocketMessage.java @@ -30,8 +30,8 @@ import org.parceler.Parcel; @JsonObject public class ErrorWebSocketMessage { @JsonField(name = "code") - String code; + public String code; @JsonField(name = "message") - String message; + public String message; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/EventOverallWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/EventOverallWebSocketMessage.java index 74c2f3ebd..27655f6c4 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/EventOverallWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/EventOverallWebSocketMessage.java @@ -31,7 +31,7 @@ import org.parceler.Parcel; @JsonObject public class EventOverallWebSocketMessage extends BaseWebSocketMessage { @JsonField(name = "type") - String type; + public String type; @JsonField(name = "event") - HashMap eventMap; + public HashMap eventMap; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseOverallWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseOverallWebSocketMessage.java index 81693df98..48c0fa25e 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseOverallWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseOverallWebSocketMessage.java @@ -30,5 +30,5 @@ import org.parceler.Parcel; @Parcel public class HelloResponseOverallWebSocketMessage extends BaseWebSocketMessage { @JsonField(name = "hello") - HelloResponseWebSocketMessage helloResponseWebSocketMessage; + public HelloResponseWebSocketMessage helloResponseWebSocketMessage; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseWebSocketMessage.java index 5d39a6541..aea61fc92 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloResponseWebSocketMessage.java @@ -30,13 +30,13 @@ import org.parceler.Parcel; @Parcel public class HelloResponseWebSocketMessage { @JsonField(name = "resumeid") - String resumeId; + public String resumeId; @JsonField(name = "sessionid") - String sessionId; + public String sessionId; @JsonField(name = "server") - ServerHelloResponseFeaturesWebSocketMessage serverHelloResponseFeaturesWebSocketMessage; + public ServerHelloResponseFeaturesWebSocketMessage serverHelloResponseFeaturesWebSocketMessage; public boolean serverHasMCUSupport() { return serverHelloResponseFeaturesWebSocketMessage != null diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/JoinedRoomOverallWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/JoinedRoomOverallWebSocketMessage.java index c5822897f..e8739aa8d 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/JoinedRoomOverallWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/JoinedRoomOverallWebSocketMessage.java @@ -30,5 +30,5 @@ import org.parceler.Parcel; @Parcel public class JoinedRoomOverallWebSocketMessage extends BaseWebSocketMessage { @JsonField(name = "room") - RoomWebSocketMessage roomWebSocketMessage; + public RoomWebSocketMessage roomWebSocketMessage; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.java index aed8d4071..8824cb76a 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.java @@ -32,8 +32,8 @@ import org.parceler.Parcel; @JsonObject public class RoomPropertiesWebSocketMessage { @JsonField(name = "name") - String name; + public String name; @JsonField(name = "type", typeConverter = EnumRoomTypeConverter.class) - Conversation.ConversationType roomType; + public Conversation.ConversationType roomType; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.java index 0dbd1f9ea..9278d9b6c 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.java @@ -30,11 +30,11 @@ import org.parceler.Parcel; @Parcel public class RoomWebSocketMessage { @JsonField(name = "roomid") - String roomId; + public String roomId; @JsonField(name = "sessionid") - String sessiondId; + public String sessiondId; @JsonField(name = "properties") - RoomPropertiesWebSocketMessage roomPropertiesWebSocketMessage; + public RoomPropertiesWebSocketMessage roomPropertiesWebSocketMessage; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ServerHelloResponseFeaturesWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ServerHelloResponseFeaturesWebSocketMessage.java index 2dbebdce7..d1dbeb2bb 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/ServerHelloResponseFeaturesWebSocketMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/ServerHelloResponseFeaturesWebSocketMessage.java @@ -31,5 +31,5 @@ import org.parceler.Parcel; @Data public class ServerHelloResponseFeaturesWebSocketMessage { @JsonField(name = "features") - List features; + public List features; } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt index 67b534f1b..affb144ea 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt @@ -37,4 +37,20 @@ class UsersRepositoryImpl(val usersDao: UsersDao): UsersRepository { override fun getUsers(): List { return usersDao.getUsers() } + + override fun getUserWithId(id: Long): UserNgEntity { + return usersDao.getUserWithId(id) + } + + override suspend fun getUserWithUsernameAndServer( + username: String, + server: String + ): UserNgEntity? { + return usersDao.getUserWithUsernameAndServer(username, server) + } + + override suspend fun updateUser(user: UserNgEntity): Int { + return usersDao.updateUser(user) + } + } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt index c9a503a8b..8ed08502b 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt @@ -25,6 +25,9 @@ import com.nextcloud.talk.newarch.local.models.UserNgEntity interface UsersRepository { fun getActiveUserLiveData(): LiveData - fun getActiveUser(): UserNgEntity + fun getActiveUser(): UserNgEntity? fun getUsers(): List + fun getUserWithId(id: Long): UserNgEntity + suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity? + suspend fun updateUser(user: UserNgEntity): Int } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt index ab1faf479..630e35c7e 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt @@ -27,6 +27,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction +import androidx.room.Update import com.nextcloud.talk.newarch.local.models.ConversationEntity @Dao @@ -39,10 +40,10 @@ abstract class ConversationsDao { abstract suspend fun clearConversationsForUser(userId: Long) @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract suspend fun saveConversation(conversation: ConversationEntity) + abstract suspend fun saveConversationWithInsert(conversation: ConversationEntity): Long @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract suspend fun saveConversations(vararg conversations: ConversationEntity) + abstract suspend fun saveConversationsWithInsert(vararg conversations: ConversationEntity): List @Query( "UPDATE conversations SET changing = :changing WHERE user = :userId AND conversation_id = :conversationId" @@ -80,17 +81,16 @@ abstract class ConversationsDao { @Transaction open suspend fun updateConversationsForUser( userId: Long, - newConversations: - Array + newConversations: Array ) { val timestamp = System.currentTimeMillis() val conversationsWithTimestampApplied = newConversations.map { - it.modifiedAt = System.currentTimeMillis() + it.modifiedAt = timestamp it } - saveConversations(*conversationsWithTimestampApplied.toTypedArray()) + saveConversationsWithInsert(*conversationsWithTimestampApplied.toTypedArray()) deleteConversationsForUserWithTimestamp(userId, timestamp) } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt index 70d909cc7..6e090daad 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt @@ -25,6 +25,9 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update +import com.nextcloud.talk.newarch.local.models.ConversationEntity import com.nextcloud.talk.newarch.local.models.MessageEntity @Dao @@ -34,5 +37,5 @@ abstract class MessagesDao { LiveData> @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract suspend fun saveMessages(vararg messages: MessageEntity) + abstract suspend fun saveMessagesWithInsert(vararg messages: MessageEntity): List } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt index 824fc5a19..e0b127aa2 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt @@ -25,6 +25,7 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Update import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.newarch.local.models.ConversationEntity import com.nextcloud.talk.newarch.local.models.UserNgEntity @@ -41,6 +42,9 @@ abstract class UsersDao { @Query("DELETE FROM users WHERE id = :userId") abstract fun deleteUserForId(userId: Long) + @Update + abstract suspend fun updateUser(user: UserNgEntity): Int + @Insert(onConflict = OnConflictStrategy.REPLACE) abstract fun saveUser(user: UserNgEntity) @@ -51,8 +55,13 @@ abstract class UsersDao { @Query("SELECT * FROM users where status != 2") abstract fun getUsers(): List + @Query("SELECT * FROM users where id = :id") + abstract fun getUserWithId(id: Long): UserNgEntity @Query("SELECT * FROM users where status = 2") abstract fun getUsersScheduledForDeletion(): List + @Query("SELECT * FROM users WHERE username = :username AND base_url = :server") + abstract suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity? + } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt index 7992c0682..1f7636e3f 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt @@ -48,7 +48,7 @@ import java.util.HashMap )] ) data class ConversationEntity( - @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long? = null, + @PrimaryKey @ColumnInfo(name = "id") var id: String, @ColumnInfo(name = "user") var user: Long? = null, @ColumnInfo(name = "conversation_id") var conversationId: String? = null, @ColumnInfo(name = "token") var token: String? = null, @@ -117,7 +117,7 @@ fun ConversationEntity.toConversation(): Conversation { } fun Conversation.toConversationEntity(): ConversationEntity { - val conversationEntity = ConversationEntity() + val conversationEntity = ConversationEntity(this.internalUserId.toString() + "@" + this.conversationId) conversationEntity.user = this.internalUserId conversationEntity.conversationId = this.conversationId conversationEntity.token = this.token diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt index 2667bfcca..76ac62e17 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt @@ -46,8 +46,8 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType )] ) data class MessageEntity( - @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long? = null, - @ColumnInfo(name = "conversation") var conversation: Long? = null, + @PrimaryKey @ColumnInfo(name = "id") var id: String, + @ColumnInfo(name = "conversation") var conversation: String, @ColumnInfo(name = "message_id") var messageId: Long = 0, @ColumnInfo(name = "actor_id") var actorId: String? = null, @ColumnInfo(name = "actor_type") var actorType: String? = null, @@ -77,8 +77,7 @@ fun MessageEntity.toChatMessage(): ChatMessage { @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) fun ChatMessage.toMessageEntity(): MessageEntity { - val messageEntity = MessageEntity(this.internalMessageId) - messageEntity.conversation = this.internalConversationId + val messageEntity = MessageEntity(this.internalConversationId + "@" + this.jsonMessageId, this.activeUser.id.toString() + "@" + this.internalConversationId) messageEntity.messageId = this.jsonMessageId messageEntity.actorType = this.actorType messageEntity.actorId = this.actorId diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java deleted file mode 100644 index 5baec10b6..000000000 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.webrtc; - -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; -import autodagger.AutoInjector; -import com.bluelinelabs.logansquare.LoganSquare; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.events.NetworkEvent; -import com.nextcloud.talk.events.WebSocketCommunicationEvent; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.models.json.participants.Participant; -import com.nextcloud.talk.models.json.signaling.NCMessageWrapper; -import com.nextcloud.talk.models.json.signaling.NCSignalingMessage; -import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage; -import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage; -import com.nextcloud.talk.models.json.websocket.CallOverallWebSocketMessage; -import com.nextcloud.talk.models.json.websocket.ErrorOverallWebSocketMessage; -import com.nextcloud.talk.models.json.websocket.EventOverallWebSocketMessage; -import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage; -import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage; -import com.nextcloud.talk.newarch.local.models.UserNgEntity; -import com.nextcloud.talk.utils.LoggingUtils; -import com.nextcloud.talk.utils.MagicMap; -import com.nextcloud.talk.utils.bundle.BundleKeys; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.inject.Inject; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; -import okio.ByteString; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -@AutoInjector(NextcloudTalkApplication.class) -public class MagicWebSocketInstance extends WebSocketListener { - private static final String TAG = "MagicWebSocketInstance"; - - @Inject - OkHttpClient okHttpClient; - - @Inject - EventBus eventBus; - - @Inject - Context context; - - private UserNgEntity conversationUser; - private String webSocketTicket; - private String resumeId; - private String sessionId; - private boolean hasMCU; - private boolean connected; - private WebSocketConnectionHelper webSocketConnectionHelper; - private WebSocket internalWebSocket; - private MagicMap magicMap; - private String connectionUrl; - - private String currentRoomToken; - private int restartCount = 0; - private boolean reconnecting = false; - - private HashMap usersHashMap; - - private List messagesQueue = new ArrayList<>(); - - MagicWebSocketInstance(UserNgEntity conversationUser, String connectionUrl, - String webSocketTicket) { - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - this.connectionUrl = connectionUrl; - this.conversationUser = conversationUser; - this.webSocketTicket = webSocketTicket; - this.webSocketConnectionHelper = new WebSocketConnectionHelper(); - this.usersHashMap = new HashMap<>(); - magicMap = new MagicMap(); - - connected = false; - eventBus.register(this); - - restartWebSocket(); - } - - private void sendHello() { - try { - if (TextUtils.isEmpty(resumeId)) { - internalWebSocket.send(LoganSquare.serialize( - webSocketConnectionHelper.getAssembledHelloModel(conversationUser, webSocketTicket))); - } else { - internalWebSocket.send(LoganSquare.serialize( - webSocketConnectionHelper.getAssembledHelloModelForResume(resumeId))); - } - } catch (IOException e) { - Log.e(TAG, "Failed to serialize hello model"); - } - } - - @Override - public void onOpen(WebSocket webSocket, Response response) { - internalWebSocket = webSocket; - sendHello(); - } - - private void closeWebSocket(WebSocket webSocket) { - webSocket.close(1000, null); - webSocket.cancel(); - if (webSocket == internalWebSocket) { - connected = false; - messagesQueue = new ArrayList<>(); - } - - restartWebSocket(); - } - - public void clearResumeId() { - resumeId = ""; - } - - public void restartWebSocket() { - reconnecting = true; - - Request request = new Request.Builder().url(connectionUrl).build(); - okHttpClient.newWebSocket(request, this); - restartCount++; - } - - @Override - public void onMessage(WebSocket webSocket, String text) { - if (webSocket == internalWebSocket) { - Log.d(TAG, "Receiving : " + webSocket.toString() + " " + text); - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "WebSocket " + webSocket.hashCode() + " receiving: " + text); - - try { - BaseWebSocketMessage baseWebSocketMessage = - LoganSquare.parse(text, BaseWebSocketMessage.class); - String messageType = baseWebSocketMessage.getType(); - switch (messageType) { - case "hello": - connected = true; - reconnecting = false; - restartCount = 0; - String oldResumeId = resumeId; - HelloResponseOverallWebSocketMessage helloResponseWebSocketMessage = - LoganSquare.parse(text, HelloResponseOverallWebSocketMessage.class); - resumeId = - helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getResumeId(); - sessionId = - helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getSessionId(); - hasMCU = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage() - .serverHasMCUSupport(); - - for (int i = 0; i < messagesQueue.size(); i++) { - webSocket.send(messagesQueue.get(i)); - } - - messagesQueue = new ArrayList<>(); - HashMap helloHasHap = new HashMap<>(); - if (!TextUtils.isEmpty(oldResumeId)) { - helloHasHap.put("oldResumeId", oldResumeId); - } else { - currentRoomToken = ""; - } - - if (!TextUtils.isEmpty(currentRoomToken)) { - helloHasHap.put("roomToken", currentRoomToken); - } - eventBus.post(new WebSocketCommunicationEvent("hello", helloHasHap)); - break; - case "error": - ErrorOverallWebSocketMessage errorOverallWebSocketMessage = - LoganSquare.parse(text, ErrorOverallWebSocketMessage.class); - if (("no_such_session").equals( - errorOverallWebSocketMessage.getErrorWebSocketMessage().getCode())) { - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "WebSocket " + webSocket.hashCode() + " resumeID " + resumeId + " expired"); - resumeId = ""; - currentRoomToken = ""; - restartWebSocket(); - } else if (("hello_expected").equals( - errorOverallWebSocketMessage.getErrorWebSocketMessage().getCode())) { - restartWebSocket(); - } - - break; - case "room": - JoinedRoomOverallWebSocketMessage joinedRoomOverallWebSocketMessage = - LoganSquare.parse(text, JoinedRoomOverallWebSocketMessage.class); - currentRoomToken = - joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage().getRoomId(); - if (joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage() - .getRoomPropertiesWebSocketMessage() != null && !TextUtils.isEmpty( - currentRoomToken)) { - sendRoomJoinedEvent(); - } - break; - case "event": - EventOverallWebSocketMessage eventOverallWebSocketMessage = - LoganSquare.parse(text, EventOverallWebSocketMessage.class); - if (eventOverallWebSocketMessage.getEventMap() != null) { - String target = (String) eventOverallWebSocketMessage.getEventMap().get("target"); - switch (target) { - case "room": - if (eventOverallWebSocketMessage.getEventMap().get("type").equals("message")) { - Map messageHashMap = - (Map) eventOverallWebSocketMessage.getEventMap() - .get("message"); - if (messageHashMap.containsKey("data")) { - Map dataHashMap = (Map) messageHashMap.get( - "data"); - if (dataHashMap.containsKey("chat")) { - boolean shouldRefreshChat; - Map chatMap = (Map) dataHashMap.get("chat"); - if (chatMap.containsKey("refresh")) { - shouldRefreshChat = (boolean) chatMap.get("refresh"); - if (shouldRefreshChat) { - HashMap refreshChatHashMap = new HashMap<>(); - refreshChatHashMap.put(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), - (String) messageHashMap.get("roomid")); - refreshChatHashMap.put(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), - Long.toString(conversationUser.getId())); - eventBus.post( - new WebSocketCommunicationEvent("refreshChat", refreshChatHashMap)); - } - } - } - } - } else if (eventOverallWebSocketMessage.getEventMap() - .get("type") - .equals("join")) { - List> joinEventMap = - (List>) eventOverallWebSocketMessage.getEventMap() - .get("join"); - HashMap internalHashMap; - Participant participant; - for (int i = 0; i < joinEventMap.size(); i++) { - internalHashMap = joinEventMap.get(i); - HashMap userMap = - (HashMap) internalHashMap.get("user"); - participant = new Participant(); - participant.setUserId((String) internalHashMap.get("userid")); - participant.setDisplayName((String) userMap.get("displayname")); - usersHashMap.put((String) internalHashMap.get("sessionid"), participant); - } - } - break; - case "participants": - if (eventOverallWebSocketMessage.getEventMap().get("type").equals("update")) { - HashMap refreshChatHashMap = new HashMap<>(); - HashMap updateEventMap = - (HashMap) eventOverallWebSocketMessage.getEventMap() - .get("update"); - refreshChatHashMap.put("roomToken", (String) updateEventMap.get("roomid")); - refreshChatHashMap.put("jobId", - Integer.toString(magicMap.add(updateEventMap.get("users")))); - eventBus.post( - new WebSocketCommunicationEvent("participantsUpdate", refreshChatHashMap)); - } - break; - } - } - break; - case "message": - CallOverallWebSocketMessage callOverallWebSocketMessage = - LoganSquare.parse(text, CallOverallWebSocketMessage.class); - NCSignalingMessage ncSignalingMessage = - callOverallWebSocketMessage.getCallWebSocketMessage().getNcSignalingMessage(); - if (TextUtils.isEmpty(ncSignalingMessage.getFrom()) - && callOverallWebSocketMessage.getCallWebSocketMessage().getSenderWebSocketMessage() - != null) { - ncSignalingMessage.setFrom(callOverallWebSocketMessage.getCallWebSocketMessage() - .getSenderWebSocketMessage() - .getSessionId()); - } - - if (!TextUtils.isEmpty(ncSignalingMessage.getFrom())) { - HashMap messageHashMap = new HashMap<>(); - messageHashMap.put("jobId", Integer.toString(magicMap.add(ncSignalingMessage))); - eventBus.post(new WebSocketCommunicationEvent("signalingMessage", messageHashMap)); - } - break; - case "bye": - connected = false; - resumeId = ""; - default: - break; - } - } catch (IOException e) { - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "WebSocket " + webSocket.hashCode() + " IOException: " + e.getMessage()); - Log.e(TAG, "Failed to recognize WebSocket message"); - } - } - } - - private void sendRoomJoinedEvent() { - HashMap joinRoomHashMap = new HashMap<>(); - joinRoomHashMap.put("roomToken", currentRoomToken); - eventBus.post(new WebSocketCommunicationEvent("roomJoined", joinRoomHashMap)); - } - - @Override - public void onMessage(WebSocket webSocket, ByteString bytes) { - Log.d(TAG, "Receiving bytes : " + bytes.hex()); - } - - @Override - public void onClosing(WebSocket webSocket, int code, String reason) { - Log.d(TAG, "Closing : " + code + " / " + reason); - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "WebSocket " + webSocket.hashCode() + " Closing: " + reason); - } - - @Override - public void onFailure(WebSocket webSocket, Throwable t, Response response) { - Log.d(TAG, "Error : " + t.getMessage()); - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "WebSocket " + webSocket.hashCode() + " onFailure: " + t.getMessage()); - closeWebSocket(webSocket); - } - - public String getSessionId() { - return sessionId; - } - - public boolean hasMCU() { - return hasMCU; - } - - public void joinRoomWithRoomTokenAndSession(String roomToken, String normalBackendSession) { - try { - String message = LoganSquare.serialize( - webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, - normalBackendSession)); - if (!connected || reconnecting) { - messagesQueue.add(message); - } else { - if (roomToken.equals(currentRoomToken)) { - sendRoomJoinedEvent(); - } else { - internalWebSocket.send(message); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void sendCallMessage(NCMessageWrapper ncMessageWrapper) { - try { - String message = LoganSquare.serialize( - webSocketConnectionHelper.getAssembledCallMessageModel(ncMessageWrapper)); - if (!connected || reconnecting) { - messagesQueue.add(message); - } else { - internalWebSocket.send(message); - } - } catch (IOException e) { - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "WebSocket sendCalLMessage: " + e.getMessage() + "\n" + ncMessageWrapper.toString()); - Log.e(TAG, "Failed to serialize signaling message"); - } - } - - public Object getJobWithId(Integer id) { - Object copyJob = magicMap.get(id); - magicMap.remove(id); - return copyJob; - } - - public void requestOfferForSessionIdWithType(String sessionIdParam, String roomType) { - try { - String message = LoganSquare.serialize( - webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType)); - if (!connected || reconnecting) { - messagesQueue.add(message); - } else { - internalWebSocket.send(message); - } - } catch (IOException e) { - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "WebSocket requestOfferForSessionIdWithType: " - + e.getMessage() - + "\n" - + sessionIdParam - + " " - + roomType); - Log.e(TAG, "Failed to offer request"); - } - } - - void sendBye() { - if (connected) { - try { - ByeWebSocketMessage byeWebSocketMessage = new ByeWebSocketMessage(); - byeWebSocketMessage.setType("bye"); - byeWebSocketMessage.setBye(new HashMap<>()); - internalWebSocket.send(LoganSquare.serialize(byeWebSocketMessage)); - } catch (IOException e) { - Log.e(TAG, "Failed to serialize bye message"); - } - } - } - - public boolean isConnected() { - return connected; - } - - public String getDisplayNameForSession(String session) { - if (usersHashMap.containsKey(session)) { - return usersHashMap.get(session).getDisplayName(); - } - - return NextcloudTalkApplication.Companion.getSharedApplication() - .getString(R.string.nc_nick_guest); - } - - public String getSessionForUserId(String userId) { - for (String session : usersHashMap.keySet()) { - if (userId.equals(usersHashMap.get(session).getUserId())) { - return session; - } - } - - return ""; - } - - public String getUserIdForSession(String session) { - if (usersHashMap.containsKey(session)) { - return usersHashMap.get(session).getUserId(); - } - - return ""; - } - - @Subscribe(threadMode = ThreadMode.BACKGROUND) - public void onMessageEvent(NetworkEvent networkEvent) { - if (networkEvent.getNetworkConnectionEvent() - .equals(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED) && !isConnected()) { - restartWebSocket(); - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.kt b/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.kt new file mode 100644 index 000000000..3108db359 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.kt @@ -0,0 +1,530 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.webrtc + +import android.content.Context +import android.text.TextUtils +import android.util.Log +import autodagger.AutoInjector +import com.bluelinelabs.logansquare.LoganSquare +import com.nextcloud.talk.R.string +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.events.NetworkEvent +import com.nextcloud.talk.events.NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED +import com.nextcloud.talk.events.WebSocketCommunicationEvent +import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.models.json.signaling.NCMessageWrapper +import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage +import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage +import com.nextcloud.talk.models.json.websocket.CallOverallWebSocketMessage +import com.nextcloud.talk.models.json.websocket.ErrorOverallWebSocketMessage +import com.nextcloud.talk.models.json.websocket.EventOverallWebSocketMessage +import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage +import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage +import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.utils.LoggingUtils.writeLogEntryToFile +import com.nextcloud.talk.utils.MagicMap +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import okhttp3.OkHttpClient +import okhttp3.Request.Builder +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import okio.ByteString +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode.BACKGROUND +import org.koin.core.KoinComponent +import org.koin.core.inject +import java.io.IOException +import java.util.ArrayList +import java.util.HashMap +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class MagicWebSocketInstance internal constructor( + conversationUser: UserNgEntity, + connectionUrl: String, + webSocketTicket: String +) : WebSocketListener(), KoinComponent { + val okHttpClient: OkHttpClient by inject() + val eventBus: EventBus by inject() + val context: Context by inject() + + private val conversationUser: UserNgEntity + private val webSocketTicket: String + private var resumeId: String? = null + var sessionId: String? = null + private set + private var hasMCU = false + var isConnected: Boolean + private set + private val webSocketConnectionHelper: WebSocketConnectionHelper + private var internalWebSocket: WebSocket? = null + private val magicMap: MagicMap + private val connectionUrl: String + private var currentRoomToken: String? = null + private var restartCount = 0 + private var reconnecting = false + private val usersHashMap: HashMap + private var messagesQueue: MutableList = + ArrayList() + + private fun sendHello() { + try { + if (TextUtils.isEmpty(resumeId)) { + internalWebSocket!!.send( + LoganSquare.serialize( + webSocketConnectionHelper.getAssembledHelloModel(conversationUser, webSocketTicket) + ) + ) + } else { + internalWebSocket!!.send( + LoganSquare.serialize( + webSocketConnectionHelper.getAssembledHelloModelForResume(resumeId) + ) + ) + } + } catch (e: IOException) { + Log.e(TAG, "Failed to serialize hello model") + } + } + + override fun onOpen( + webSocket: WebSocket, + response: Response + ) { + internalWebSocket = webSocket + sendHello() + } + + private fun closeWebSocket(webSocket: WebSocket) { + webSocket.close(1000, null) + webSocket.cancel() + if (webSocket === internalWebSocket) { + isConnected = false + messagesQueue = ArrayList() + } + restartWebSocket() + } + + fun clearResumeId() { + resumeId = "" + } + + fun restartWebSocket() { + reconnecting = true + val request = Builder() + .url(connectionUrl) + .build() + okHttpClient.newWebSocket(request, this) + restartCount++ + } + + override fun onMessage( + webSocket: WebSocket, + text: String + ) { + if (webSocket === internalWebSocket) { + Log.d( + TAG, "Receiving : $webSocket $text" + ) + writeLogEntryToFile( + context, + "WebSocket " + webSocket.hashCode() + " receiving: " + text + ) + try { + val baseWebSocketMessage = + LoganSquare.parse(text, BaseWebSocketMessage::class.java) + val messageType = baseWebSocketMessage.type + when (messageType) { + "hello" -> { + isConnected = true + reconnecting = false + restartCount = 0 + val oldResumeId = resumeId + val helloResponseWebSocketMessage = + LoganSquare.parse( + text, HelloResponseOverallWebSocketMessage::class.java + ) + resumeId = helloResponseWebSocketMessage.helloResponseWebSocketMessage + .resumeId + sessionId = helloResponseWebSocketMessage.helloResponseWebSocketMessage + .sessionId + hasMCU = helloResponseWebSocketMessage.helloResponseWebSocketMessage + .serverHasMCUSupport() + var i = 0 + while (i < messagesQueue.size) { + webSocket.send(messagesQueue[i]) + i++ + } + messagesQueue = ArrayList() + val helloHasHap = + HashMap() + if (!TextUtils.isEmpty(oldResumeId)) { + helloHasHap["oldResumeId"] = oldResumeId + } else { + currentRoomToken = "" + } + if (!TextUtils.isEmpty(currentRoomToken)) { + helloHasHap["roomToken"] = currentRoomToken + } + eventBus.post(WebSocketCommunicationEvent("hello", helloHasHap)) + } + "error" -> { + val errorOverallWebSocketMessage = + LoganSquare.parse( + text, ErrorOverallWebSocketMessage::class.java + ) + if ("no_such_session" == + errorOverallWebSocketMessage.errorWebSocketMessage.code + ) { + writeLogEntryToFile( + context, + "WebSocket " + webSocket.hashCode() + " resumeID " + resumeId + " expired" + ) + resumeId = "" + currentRoomToken = "" + restartWebSocket() + } else if ("hello_expected" == + errorOverallWebSocketMessage.errorWebSocketMessage.code + ) { + restartWebSocket() + } + } + "room" -> { + val joinedRoomOverallWebSocketMessage = + LoganSquare.parse( + text, JoinedRoomOverallWebSocketMessage::class.java + ) + currentRoomToken = joinedRoomOverallWebSocketMessage.roomWebSocketMessage + .roomId + if (joinedRoomOverallWebSocketMessage.roomWebSocketMessage + .roomPropertiesWebSocketMessage != null && !TextUtils.isEmpty( + currentRoomToken + ) + ) { + sendRoomJoinedEvent() + } + } + "event" -> { + val eventOverallWebSocketMessage = + LoganSquare.parse( + text, EventOverallWebSocketMessage::class.java + ) + if (eventOverallWebSocketMessage.eventMap != null) { + val target = + eventOverallWebSocketMessage.eventMap["target"] as String? + when (target) { + "room" -> if (eventOverallWebSocketMessage.eventMap["type"] == "message" + ) { + val messageHashMap = + eventOverallWebSocketMessage.eventMap["message"] as Map? + if (messageHashMap!!.containsKey("data")) { + val dataHashMap = + messageHashMap["data"] as Map? + if (dataHashMap!!.containsKey("chat")) { + val shouldRefreshChat: Boolean + val chatMap = + dataHashMap["chat"] as Map? + if (chatMap!!.containsKey("refresh")) { + shouldRefreshChat = chatMap["refresh"] as Boolean + if (shouldRefreshChat) { + val refreshChatHashMap = + HashMap() + refreshChatHashMap[KEY_ROOM_TOKEN] = messageHashMap["roomid"] as String? + refreshChatHashMap[KEY_INTERNAL_USER_ID] = + java.lang.Long.toString(conversationUser.id) + eventBus.post( + WebSocketCommunicationEvent("refreshChat", refreshChatHashMap) + ) + } + } + } + } + } else if (eventOverallWebSocketMessage.eventMap["type"] + == "join" + ) { + val joinEventMap = + eventOverallWebSocketMessage.eventMap["join"] as List>? + var internalHashMap: HashMap + var participant: Participant + var i = 0 + while (i < joinEventMap!!.size) { + internalHashMap = joinEventMap[i] + val userMap = + internalHashMap["user"] as HashMap? + participant = Participant() + participant.userId = internalHashMap["userid"] as String + participant.displayName = userMap!!["displayname"] as String + usersHashMap[internalHashMap["sessionid"] as String?] = participant + i++ + } + } + "participants" -> if (eventOverallWebSocketMessage.eventMap["type"] == "update" + ) { + val refreshChatHashMap = + HashMap() + val updateEventMap = + eventOverallWebSocketMessage.eventMap["update"] as HashMap? + refreshChatHashMap["roomToken"] = updateEventMap!!["roomid"] as String? + refreshChatHashMap["jobId"] = Integer.toString( + magicMap.add( + updateEventMap["users"]!! + ) + ) + eventBus.post( + WebSocketCommunicationEvent("participantsUpdate", refreshChatHashMap) + ) + } + } + } + } + "message" -> { + val callOverallWebSocketMessage = + LoganSquare.parse( + text, CallOverallWebSocketMessage::class.java + ) + val ncSignalingMessage = + callOverallWebSocketMessage.callWebSocketMessage + .ncSignalingMessage + if (TextUtils.isEmpty(ncSignalingMessage.from) + && callOverallWebSocketMessage.callWebSocketMessage.senderWebSocketMessage + != null + ) { + ncSignalingMessage.from = + callOverallWebSocketMessage.callWebSocketMessage + .senderWebSocketMessage + .sessionId + + } + if (!TextUtils.isEmpty(ncSignalingMessage.from)) { + val messageHashMap = + HashMap() + messageHashMap["jobId"] = Integer.toString(magicMap.add(ncSignalingMessage)) + eventBus.post(WebSocketCommunicationEvent("signalingMessage", messageHashMap)) + } + } + "bye" -> { + isConnected = false + resumeId = "" + } + else -> { + } + } + } catch (e: IOException) { + writeLogEntryToFile( + context, + "WebSocket " + webSocket.hashCode() + " IOException: " + e.message + ) + Log.e( + TAG, "Failed to recognize WebSocket message" + ) + } + } + } + + private fun sendRoomJoinedEvent() { + val joinRoomHashMap = + HashMap() + joinRoomHashMap["roomToken"] = currentRoomToken + eventBus.post(WebSocketCommunicationEvent("roomJoined", joinRoomHashMap)) + } + + override fun onMessage( + webSocket: WebSocket, + bytes: ByteString + ) { + Log.d(TAG, "Receiving bytes : " + bytes.hex()) + } + + override fun onClosing( + webSocket: WebSocket, + code: Int, + reason: String + ) { + Log.d(TAG, "Closing : $code / $reason") + writeLogEntryToFile( + context, + "WebSocket " + webSocket.hashCode() + " Closing: " + reason + ) + } + + override fun onFailure( + webSocket: WebSocket, + t: Throwable, + response: Response? + ) { + Log.d(TAG, "Error : " + t.message) + writeLogEntryToFile( + context, + "WebSocket " + webSocket.hashCode() + " onFailure: " + t.message + ) + closeWebSocket(webSocket) + } + + fun hasMCU(): Boolean { + return hasMCU + } + + fun joinRoomWithRoomTokenAndSession( + roomToken: String, + normalBackendSession: String? + ) { + try { + val message = LoganSquare.serialize( + webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel( + roomToken, + normalBackendSession + ) + ) + if (!isConnected || reconnecting) { + messagesQueue.add(message) + } else { + if (roomToken == currentRoomToken) { + sendRoomJoinedEvent() + } else { + internalWebSocket!!.send(message) + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + + fun sendCallMessage(ncMessageWrapper: NCMessageWrapper) { + try { + val message = LoganSquare.serialize( + webSocketConnectionHelper.getAssembledCallMessageModel(ncMessageWrapper) + ) + if (!isConnected || reconnecting) { + messagesQueue.add(message) + } else { + internalWebSocket!!.send(message) + } + } catch (e: IOException) { + writeLogEntryToFile( + context, + "WebSocket sendCalLMessage: " + e.message + "\n" + ncMessageWrapper.toString() + ) + Log.e( + TAG, "Failed to serialize signaling message" + ) + } + } + + fun getJobWithId(id: Int): Any? { + val copyJob = magicMap[id] + magicMap.remove(id) + return copyJob + } + + fun requestOfferForSessionIdWithType( + sessionIdParam: String, + roomType: String + ) { + try { + val message = LoganSquare.serialize( + webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType) + ) + if (!isConnected || reconnecting) { + messagesQueue.add(message) + } else { + internalWebSocket!!.send(message) + } + } catch (e: IOException) { + writeLogEntryToFile( + context, + "WebSocket requestOfferForSessionIdWithType: " + + e.message + + "\n" + + sessionIdParam + + " " + + roomType + ) + Log.e(TAG, "Failed to offer request") + } + } + + fun sendBye() { + if (isConnected) { + try { + val byeWebSocketMessage = ByeWebSocketMessage() + byeWebSocketMessage.type = "bye" + byeWebSocketMessage.bye = HashMap() + internalWebSocket!!.send(LoganSquare.serialize(byeWebSocketMessage)) + } catch (e: IOException) { + Log.e(TAG, "Failed to serialize bye message") + } + } + } + + fun getDisplayNameForSession(session: String?): String { + return if (usersHashMap.containsKey(session)) { + usersHashMap[session]!!.displayName + } else sharedApplication!!.getString(string.nc_nick_guest) + } + + fun getSessionForUserId(userId: String): String? { + for (session in usersHashMap.keys) { + if (userId == usersHashMap[session]!!.userId) { + return session + } + } + return "" + } + + fun getUserIdForSession(session: String?): String { + return if (usersHashMap.containsKey(session)) { + usersHashMap[session]!!.userId + } else "" + } + + @Subscribe(threadMode = BACKGROUND) + fun onMessageEvent(networkEvent: NetworkEvent) { + if ((networkEvent.networkConnectionEvent + == NETWORK_CONNECTED) && !isConnected + ) { + restartWebSocket() + } + } + + companion object { + private const val TAG = "MagicWebSocketInstance" + } + + init { + sharedApplication + ?.componentApplication + ?.inject(this) + this.connectionUrl = connectionUrl + this.conversationUser = conversationUser + this.webSocketTicket = webSocketTicket + webSocketConnectionHelper = WebSocketConnectionHelper() + usersHashMap = + HashMap() + magicMap = MagicMap() + isConnected = false + eventBus.register(this) + restartWebSocket() + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 15171f535..a8dc25248 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,7 +29,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-XX:MaxHeapSize\=4096m -Xmx4096m +org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m android.useAndroidX=true android.enableJetifier=true