mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-21 11:45:03 +01:00
Significant changes
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
d5cf261776
commit
8eb2f78a23
@ -2,17 +2,17 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "c81b4edb8abdd29b77836d16a7d991c2",
|
"identityHash": "c7b1b47572d7ace1b422d0a3887a54e1",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "conversations",
|
"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": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
"columnName": "id",
|
"columnName": "id",
|
||||||
"affinity": "INTEGER",
|
"affinity": "TEXT",
|
||||||
"notNull": false
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "user",
|
"fieldPath": "user",
|
||||||
@ -169,7 +169,7 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"autoGenerate": true
|
"autoGenerate": false
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
@ -198,19 +198,19 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "messages",
|
"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": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
"columnName": "id",
|
"columnName": "id",
|
||||||
"affinity": "INTEGER",
|
"affinity": "TEXT",
|
||||||
"notNull": false
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "conversation",
|
"fieldPath": "conversation",
|
||||||
"columnName": "conversation",
|
"columnName": "conversation",
|
||||||
"affinity": "INTEGER",
|
"affinity": "TEXT",
|
||||||
"notNull": false
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "messageId",
|
"fieldPath": "messageId",
|
||||||
@ -259,7 +259,7 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"autoGenerate": true
|
"autoGenerate": false
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
@ -369,7 +369,7 @@
|
|||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -118,6 +118,7 @@ import org.apache.commons.lang3.StringEscapeUtils
|
|||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
import org.parceler.Parcel
|
import org.parceler.Parcel
|
||||||
import org.webrtc.AudioSource
|
import org.webrtc.AudioSource
|
||||||
import org.webrtc.AudioTrack
|
import org.webrtc.AudioTrack
|
||||||
@ -194,10 +195,8 @@ class CallController(args: Bundle) : BaseController() {
|
|||||||
@JvmField
|
@JvmField
|
||||||
@Inject
|
@Inject
|
||||||
var userUtils: UserUtils? = null
|
var userUtils: UserUtils? = null
|
||||||
@JvmField
|
|
||||||
@Inject
|
|
||||||
var cookieManager: CookieManager? = null
|
|
||||||
|
|
||||||
|
val cookieManager: CookieManager by inject()
|
||||||
private var peerConnectionFactory: PeerConnectionFactory? = null
|
private var peerConnectionFactory: PeerConnectionFactory? = null
|
||||||
private var audioConstraints: MediaConstraints? = null
|
private var audioConstraints: MediaConstraints? = null
|
||||||
private var videoConstraints: MediaConstraints? = null
|
private var videoConstraints: MediaConstraints? = null
|
||||||
@ -214,7 +213,7 @@ class CallController(args: Bundle) : BaseController() {
|
|||||||
private var pingDisposable: Disposable? = null
|
private var pingDisposable: Disposable? = null
|
||||||
private var iceServers: MutableList<PeerConnection.IceServer>? = null
|
private var iceServers: MutableList<PeerConnection.IceServer>? = null
|
||||||
private var cameraEnumerator: CameraEnumerator? = null
|
private var cameraEnumerator: CameraEnumerator? = null
|
||||||
private var roomToken: String? = null
|
private var roomToken: String
|
||||||
private val conversationUser: UserNgEntity?
|
private val conversationUser: UserNgEntity?
|
||||||
private var callSession: String? = null
|
private var callSession: String? = null
|
||||||
private var localMediaStream: MediaStream? = null
|
private var localMediaStream: MediaStream? = null
|
||||||
@ -416,7 +415,7 @@ class CallController(args: Bundle) : BaseController() {
|
|||||||
override fun onNext(roomsOverall: RoomsOverall) {
|
override fun onNext(roomsOverall: RoomsOverall) {
|
||||||
for (conversation in roomsOverall.ocs.data) {
|
for (conversation in roomsOverall.ocs.data) {
|
||||||
if (roomId == conversation.conversationId) {
|
if (roomId == conversation.conversationId) {
|
||||||
roomToken = conversation.token
|
roomToken = conversation.token.toString()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1321,11 +1320,13 @@ class CallController(args: Bundle) : BaseController() {
|
|||||||
Integer.valueOf(webSocketCommunicationEvent.hashMap!!["jobId"]!!)
|
Integer.valueOf(webSocketCommunicationEvent.hashMap!!["jobId"]!!)
|
||||||
) as NCSignalingMessage
|
) as NCSignalingMessage
|
||||||
)
|
)
|
||||||
"peerReadyForRequestingOffer" -> webSocketClient!!.requestOfferForSessionIdWithType(
|
"peerReadyForRequestingOffer" -> webSocketCommunicationEvent.hashMap!!["sessionId"]?.let {
|
||||||
webSocketCommunicationEvent.hashMap!!["sessionId"], "video"
|
webSocketClient!!.requestOfferForSessionIdWithType(
|
||||||
|
it, "video"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OnClick(R.id.pip_video_view, R.id.remote_renderers_layout)
|
@OnClick(R.id.pip_video_view, R.id.remote_renderers_layout)
|
||||||
fun showCallControls() {
|
fun showCallControls() {
|
||||||
@ -2166,7 +2167,7 @@ class CallController(args: Bundle) : BaseController() {
|
|||||||
var sessionOrUserId = sessionOrUserId
|
var sessionOrUserId = sessionOrUserId
|
||||||
if (isFromAnEvent && hasExternalSignalingServer) {
|
if (isFromAnEvent && hasExternalSignalingServer) {
|
||||||
// get session based on userId
|
// get session based on userId
|
||||||
sessionOrUserId = webSocketClient!!.getSessionForUserId(sessionOrUserId)
|
sessionOrUserId = webSocketClient!!.getSessionForUserId(sessionOrUserId).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionOrUserId += "+$type"
|
sessionOrUserId += "+$type"
|
||||||
|
@ -852,8 +852,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (magicWebSocketInstance != null) {
|
if (magicWebSocketInstance != null) {
|
||||||
magicWebSocketInstance?.joinRoomWithRoomTokenAndSession(
|
magicWebSocketInstance?.joinRoomWithRoomTokenAndSession(roomToken!!,
|
||||||
roomToken, currentConversation?.sessionId
|
currentConversation?.sessionId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (startCallFromNotification != null && startCallFromNotification == true) {
|
if (startCallFromNotification != null && startCallFromNotification == true) {
|
||||||
@ -874,7 +874,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
inConversation = true
|
inConversation = true
|
||||||
if (magicWebSocketInstance != null) {
|
if (magicWebSocketInstance != null) {
|
||||||
magicWebSocketInstance?.joinRoomWithRoomTokenAndSession(
|
magicWebSocketInstance?.joinRoomWithRoomTokenAndSession(
|
||||||
roomToken,
|
roomToken!!,
|
||||||
currentConversation?.sessionId
|
currentConversation?.sessionId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,6 @@ import com.nextcloud.talk.events.EventStatus
|
|||||||
import com.nextcloud.talk.interfaces.ConversationInfoInterface
|
import com.nextcloud.talk.interfaces.ConversationInfoInterface
|
||||||
import com.nextcloud.talk.jobs.DeleteConversationWorker
|
import com.nextcloud.talk.jobs.DeleteConversationWorker
|
||||||
import com.nextcloud.talk.jobs.LeaveConversationWorker
|
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
|
||||||
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
|
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
|
||||||
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.GROUP_CONVERSATION
|
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.models.json.participants.ParticipantsOverall
|
||||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
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.ApiUtils
|
||||||
import com.nextcloud.talk.utils.DateUtils
|
import com.nextcloud.talk.utils.DateUtils
|
||||||
import com.nextcloud.talk.utils.DisplayUtils
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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<Persistable> 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<String, String> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<String, String> = 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"
|
||||||
|
}
|
||||||
|
}
|
@ -29,4 +29,10 @@ public class WebSocketCommunicationEvent {
|
|||||||
public final String type;
|
public final String type;
|
||||||
@Nullable
|
@Nullable
|
||||||
public final HashMap<String, String> hashMap;
|
public final HashMap<String, String> hashMap;
|
||||||
|
|
||||||
|
public WebSocketCommunicationEvent(String type,
|
||||||
|
@Nullable HashMap<String, String> hashMap) {
|
||||||
|
this.type = type;
|
||||||
|
this.hashMap = hashMap;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Nextcloud Talk application
|
|
||||||
*
|
|
||||||
* @author Mario Danic
|
|
||||||
* Copyright (C) 2017-2018 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<String> 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<Void>() {
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Mario Danic
|
||||||
|
* Copyright (C) 2017-2018 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<String>()
|
||||||
|
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<Void?> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -18,15 +18,17 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.nextcloud.talk.models;
|
package com.nextcloud.talk.models
|
||||||
|
|
||||||
import lombok.Data;
|
import android.os.Parcelable
|
||||||
import org.parceler.Parcel;
|
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;
|
|
||||||
}
|
|
@ -65,9 +65,9 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Long internalUserId = null;
|
public Long internalUserId = null;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Long internalMessageId = null;
|
public String internalMessageId = null;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Long internalConversationId = null;
|
public String internalConversationId = null;
|
||||||
@JsonField(name = "id")
|
@JsonField(name = "id")
|
||||||
@Ignore
|
@Ignore
|
||||||
public Long jsonMessageId;
|
public Long jsonMessageId;
|
||||||
|
@ -30,11 +30,11 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class ActorWebSocketMessage {
|
public class ActorWebSocketMessage {
|
||||||
@JsonField(name = "type")
|
@JsonField(name = "type")
|
||||||
String type;
|
public String type;
|
||||||
|
|
||||||
@JsonField(name = "sessionid")
|
@JsonField(name = "sessionid")
|
||||||
String sessionId;
|
public String sessionId;
|
||||||
|
|
||||||
@JsonField(name = "userid")
|
@JsonField(name = "userid")
|
||||||
String userid;
|
public String userid;
|
||||||
}
|
}
|
||||||
|
@ -30,5 +30,5 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class BaseWebSocketMessage {
|
public class BaseWebSocketMessage {
|
||||||
@JsonField(name = "type")
|
@JsonField(name = "type")
|
||||||
String type;
|
public String type;
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,5 @@ import org.parceler.Parcel;
|
|||||||
@Data
|
@Data
|
||||||
public class ByeWebSocketMessage extends BaseWebSocketMessage {
|
public class ByeWebSocketMessage extends BaseWebSocketMessage {
|
||||||
@JsonField(name = "bye")
|
@JsonField(name = "bye")
|
||||||
HashMap<String, Object> bye;
|
public HashMap<String, Object> bye;
|
||||||
}
|
}
|
||||||
|
@ -30,5 +30,5 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class CallOverallWebSocketMessage extends BaseWebSocketMessage {
|
public class CallOverallWebSocketMessage extends BaseWebSocketMessage {
|
||||||
@JsonField(name = "message")
|
@JsonField(name = "message")
|
||||||
CallWebSocketMessage callWebSocketMessage;
|
public CallWebSocketMessage callWebSocketMessage;
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,11 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class CallWebSocketMessage {
|
public class CallWebSocketMessage {
|
||||||
@JsonField(name = "recipient")
|
@JsonField(name = "recipient")
|
||||||
ActorWebSocketMessage recipientWebSocketMessage;
|
public ActorWebSocketMessage recipientWebSocketMessage;
|
||||||
|
|
||||||
@JsonField(name = "sender")
|
@JsonField(name = "sender")
|
||||||
ActorWebSocketMessage senderWebSocketMessage;
|
public ActorWebSocketMessage senderWebSocketMessage;
|
||||||
|
|
||||||
@JsonField(name = "data")
|
@JsonField(name = "data")
|
||||||
NCSignalingMessage ncSignalingMessage;
|
public NCSignalingMessage ncSignalingMessage;
|
||||||
}
|
}
|
||||||
|
@ -30,5 +30,5 @@ import org.parceler.Parcel;
|
|||||||
@JsonObject
|
@JsonObject
|
||||||
public class ErrorOverallWebSocketMessage extends BaseWebSocketMessage {
|
public class ErrorOverallWebSocketMessage extends BaseWebSocketMessage {
|
||||||
@JsonField(name = "error")
|
@JsonField(name = "error")
|
||||||
ErrorWebSocketMessage errorWebSocketMessage;
|
public ErrorWebSocketMessage errorWebSocketMessage;
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ import org.parceler.Parcel;
|
|||||||
@JsonObject
|
@JsonObject
|
||||||
public class ErrorWebSocketMessage {
|
public class ErrorWebSocketMessage {
|
||||||
@JsonField(name = "code")
|
@JsonField(name = "code")
|
||||||
String code;
|
public String code;
|
||||||
|
|
||||||
@JsonField(name = "message")
|
@JsonField(name = "message")
|
||||||
String message;
|
public String message;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import org.parceler.Parcel;
|
|||||||
@JsonObject
|
@JsonObject
|
||||||
public class EventOverallWebSocketMessage extends BaseWebSocketMessage {
|
public class EventOverallWebSocketMessage extends BaseWebSocketMessage {
|
||||||
@JsonField(name = "type")
|
@JsonField(name = "type")
|
||||||
String type;
|
public String type;
|
||||||
@JsonField(name = "event")
|
@JsonField(name = "event")
|
||||||
HashMap<String, Object> eventMap;
|
public HashMap<String, Object> eventMap;
|
||||||
}
|
}
|
||||||
|
@ -30,5 +30,5 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class HelloResponseOverallWebSocketMessage extends BaseWebSocketMessage {
|
public class HelloResponseOverallWebSocketMessage extends BaseWebSocketMessage {
|
||||||
@JsonField(name = "hello")
|
@JsonField(name = "hello")
|
||||||
HelloResponseWebSocketMessage helloResponseWebSocketMessage;
|
public HelloResponseWebSocketMessage helloResponseWebSocketMessage;
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,13 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class HelloResponseWebSocketMessage {
|
public class HelloResponseWebSocketMessage {
|
||||||
@JsonField(name = "resumeid")
|
@JsonField(name = "resumeid")
|
||||||
String resumeId;
|
public String resumeId;
|
||||||
|
|
||||||
@JsonField(name = "sessionid")
|
@JsonField(name = "sessionid")
|
||||||
String sessionId;
|
public String sessionId;
|
||||||
|
|
||||||
@JsonField(name = "server")
|
@JsonField(name = "server")
|
||||||
ServerHelloResponseFeaturesWebSocketMessage serverHelloResponseFeaturesWebSocketMessage;
|
public ServerHelloResponseFeaturesWebSocketMessage serverHelloResponseFeaturesWebSocketMessage;
|
||||||
|
|
||||||
public boolean serverHasMCUSupport() {
|
public boolean serverHasMCUSupport() {
|
||||||
return serverHelloResponseFeaturesWebSocketMessage != null
|
return serverHelloResponseFeaturesWebSocketMessage != null
|
||||||
|
@ -30,5 +30,5 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class JoinedRoomOverallWebSocketMessage extends BaseWebSocketMessage {
|
public class JoinedRoomOverallWebSocketMessage extends BaseWebSocketMessage {
|
||||||
@JsonField(name = "room")
|
@JsonField(name = "room")
|
||||||
RoomWebSocketMessage roomWebSocketMessage;
|
public RoomWebSocketMessage roomWebSocketMessage;
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,8 @@ import org.parceler.Parcel;
|
|||||||
@JsonObject
|
@JsonObject
|
||||||
public class RoomPropertiesWebSocketMessage {
|
public class RoomPropertiesWebSocketMessage {
|
||||||
@JsonField(name = "name")
|
@JsonField(name = "name")
|
||||||
String name;
|
public String name;
|
||||||
|
|
||||||
@JsonField(name = "type", typeConverter = EnumRoomTypeConverter.class)
|
@JsonField(name = "type", typeConverter = EnumRoomTypeConverter.class)
|
||||||
Conversation.ConversationType roomType;
|
public Conversation.ConversationType roomType;
|
||||||
}
|
}
|
||||||
|
@ -30,11 +30,11 @@ import org.parceler.Parcel;
|
|||||||
@Parcel
|
@Parcel
|
||||||
public class RoomWebSocketMessage {
|
public class RoomWebSocketMessage {
|
||||||
@JsonField(name = "roomid")
|
@JsonField(name = "roomid")
|
||||||
String roomId;
|
public String roomId;
|
||||||
|
|
||||||
@JsonField(name = "sessionid")
|
@JsonField(name = "sessionid")
|
||||||
String sessiondId;
|
public String sessiondId;
|
||||||
|
|
||||||
@JsonField(name = "properties")
|
@JsonField(name = "properties")
|
||||||
RoomPropertiesWebSocketMessage roomPropertiesWebSocketMessage;
|
public RoomPropertiesWebSocketMessage roomPropertiesWebSocketMessage;
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,5 @@ import org.parceler.Parcel;
|
|||||||
@Data
|
@Data
|
||||||
public class ServerHelloResponseFeaturesWebSocketMessage {
|
public class ServerHelloResponseFeaturesWebSocketMessage {
|
||||||
@JsonField(name = "features")
|
@JsonField(name = "features")
|
||||||
List<String> features;
|
public List<String> features;
|
||||||
}
|
}
|
||||||
|
@ -37,4 +37,20 @@ class UsersRepositoryImpl(val usersDao: UsersDao): UsersRepository {
|
|||||||
override fun getUsers(): List<UserNgEntity> {
|
override fun getUsers(): List<UserNgEntity> {
|
||||||
return usersDao.getUsers()
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
|||||||
|
|
||||||
interface UsersRepository {
|
interface UsersRepository {
|
||||||
fun getActiveUserLiveData(): LiveData<UserNgEntity>
|
fun getActiveUserLiveData(): LiveData<UserNgEntity>
|
||||||
fun getActiveUser(): UserNgEntity
|
fun getActiveUser(): UserNgEntity?
|
||||||
fun getUsers(): List<UserNgEntity>
|
fun getUsers(): List<UserNgEntity>
|
||||||
|
fun getUserWithId(id: Long): UserNgEntity
|
||||||
|
suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity?
|
||||||
|
suspend fun updateUser(user: UserNgEntity): Int
|
||||||
}
|
}
|
@ -27,6 +27,7 @@ import androidx.room.Insert
|
|||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import com.nextcloud.talk.newarch.local.models.ConversationEntity
|
import com.nextcloud.talk.newarch.local.models.ConversationEntity
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@ -39,10 +40,10 @@ abstract class ConversationsDao {
|
|||||||
abstract suspend fun clearConversationsForUser(userId: Long)
|
abstract suspend fun clearConversationsForUser(userId: Long)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
abstract suspend fun saveConversation(conversation: ConversationEntity)
|
abstract suspend fun saveConversationWithInsert(conversation: ConversationEntity): Long
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
abstract suspend fun saveConversations(vararg conversations: ConversationEntity)
|
abstract suspend fun saveConversationsWithInsert(vararg conversations: ConversationEntity): List<Long>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE conversations SET changing = :changing WHERE user = :userId AND conversation_id = :conversationId"
|
"UPDATE conversations SET changing = :changing WHERE user = :userId AND conversation_id = :conversationId"
|
||||||
@ -80,17 +81,16 @@ abstract class ConversationsDao {
|
|||||||
@Transaction
|
@Transaction
|
||||||
open suspend fun updateConversationsForUser(
|
open suspend fun updateConversationsForUser(
|
||||||
userId: Long,
|
userId: Long,
|
||||||
newConversations:
|
newConversations: Array<ConversationEntity>
|
||||||
Array<ConversationEntity>
|
|
||||||
) {
|
) {
|
||||||
val timestamp = System.currentTimeMillis()
|
val timestamp = System.currentTimeMillis()
|
||||||
|
|
||||||
val conversationsWithTimestampApplied = newConversations.map {
|
val conversationsWithTimestampApplied = newConversations.map {
|
||||||
it.modifiedAt = System.currentTimeMillis()
|
it.modifiedAt = timestamp
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
saveConversations(*conversationsWithTimestampApplied.toTypedArray())
|
saveConversationsWithInsert(*conversationsWithTimestampApplied.toTypedArray())
|
||||||
deleteConversationsForUserWithTimestamp(userId, timestamp)
|
deleteConversationsForUserWithTimestamp(userId, timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,6 +25,9 @@ import androidx.room.Dao
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
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
|
import com.nextcloud.talk.newarch.local.models.MessageEntity
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@ -34,5 +37,5 @@ abstract class MessagesDao {
|
|||||||
LiveData<List<MessageEntity>>
|
LiveData<List<MessageEntity>>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
abstract suspend fun saveMessages(vararg messages: MessageEntity)
|
abstract suspend fun saveMessagesWithInsert(vararg messages: MessageEntity): List<Long>
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ import androidx.room.Dao
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
import com.nextcloud.talk.models.database.UserEntity
|
import com.nextcloud.talk.models.database.UserEntity
|
||||||
import com.nextcloud.talk.newarch.local.models.ConversationEntity
|
import com.nextcloud.talk.newarch.local.models.ConversationEntity
|
||||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||||
@ -41,6 +42,9 @@ abstract class UsersDao {
|
|||||||
@Query("DELETE FROM users WHERE id = :userId")
|
@Query("DELETE FROM users WHERE id = :userId")
|
||||||
abstract fun deleteUserForId(userId: Long)
|
abstract fun deleteUserForId(userId: Long)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract suspend fun updateUser(user: UserNgEntity): Int
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
abstract fun saveUser(user: UserNgEntity)
|
abstract fun saveUser(user: UserNgEntity)
|
||||||
|
|
||||||
@ -51,8 +55,13 @@ abstract class UsersDao {
|
|||||||
@Query("SELECT * FROM users where status != 2")
|
@Query("SELECT * FROM users where status != 2")
|
||||||
abstract fun getUsers(): List<UserNgEntity>
|
abstract fun getUsers(): List<UserNgEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM users where id = :id")
|
||||||
|
abstract fun getUserWithId(id: Long): UserNgEntity
|
||||||
|
|
||||||
@Query("SELECT * FROM users where status = 2")
|
@Query("SELECT * FROM users where status = 2")
|
||||||
abstract fun getUsersScheduledForDeletion(): List<UserNgEntity>
|
abstract fun getUsersScheduledForDeletion(): List<UserNgEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM users WHERE username = :username AND base_url = :server")
|
||||||
|
abstract suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity?
|
||||||
|
|
||||||
}
|
}
|
@ -48,7 +48,7 @@ import java.util.HashMap
|
|||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
data class ConversationEntity(
|
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 = "user") var user: Long? = null,
|
||||||
@ColumnInfo(name = "conversation_id") var conversationId: String? = null,
|
@ColumnInfo(name = "conversation_id") var conversationId: String? = null,
|
||||||
@ColumnInfo(name = "token") var token: String? = null,
|
@ColumnInfo(name = "token") var token: String? = null,
|
||||||
@ -117,7 +117,7 @@ fun ConversationEntity.toConversation(): Conversation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Conversation.toConversationEntity(): ConversationEntity {
|
fun Conversation.toConversationEntity(): ConversationEntity {
|
||||||
val conversationEntity = ConversationEntity()
|
val conversationEntity = ConversationEntity(this.internalUserId.toString() + "@" + this.conversationId)
|
||||||
conversationEntity.user = this.internalUserId
|
conversationEntity.user = this.internalUserId
|
||||||
conversationEntity.conversationId = this.conversationId
|
conversationEntity.conversationId = this.conversationId
|
||||||
conversationEntity.token = this.token
|
conversationEntity.token = this.token
|
||||||
|
@ -46,8 +46,8 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType
|
|||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
data class MessageEntity(
|
data class MessageEntity(
|
||||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long? = null,
|
@PrimaryKey @ColumnInfo(name = "id") var id: String,
|
||||||
@ColumnInfo(name = "conversation") var conversation: Long? = null,
|
@ColumnInfo(name = "conversation") var conversation: String,
|
||||||
@ColumnInfo(name = "message_id") var messageId: Long = 0,
|
@ColumnInfo(name = "message_id") var messageId: Long = 0,
|
||||||
@ColumnInfo(name = "actor_id") var actorId: String? = null,
|
@ColumnInfo(name = "actor_id") var actorId: String? = null,
|
||||||
@ColumnInfo(name = "actor_type") var actorType: String? = null,
|
@ColumnInfo(name = "actor_type") var actorType: String? = null,
|
||||||
@ -77,8 +77,7 @@ fun MessageEntity.toChatMessage(): ChatMessage {
|
|||||||
|
|
||||||
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
||||||
fun ChatMessage.toMessageEntity(): MessageEntity {
|
fun ChatMessage.toMessageEntity(): MessageEntity {
|
||||||
val messageEntity = MessageEntity(this.internalMessageId)
|
val messageEntity = MessageEntity(this.internalConversationId + "@" + this.jsonMessageId, this.activeUser.id.toString() + "@" + this.internalConversationId)
|
||||||
messageEntity.conversation = this.internalConversationId
|
|
||||||
messageEntity.messageId = this.jsonMessageId
|
messageEntity.messageId = this.jsonMessageId
|
||||||
messageEntity.actorType = this.actorType
|
messageEntity.actorType = this.actorType
|
||||||
messageEntity.actorId = this.actorId
|
messageEntity.actorId = this.actorId
|
||||||
|
@ -1,473 +0,0 @@
|
|||||||
/*
|
|
||||||
* Nextcloud Talk application
|
|
||||||
*
|
|
||||||
* @author Mario Danic
|
|
||||||
* Copyright (C) 2017-2018 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<String, Participant> usersHashMap;
|
|
||||||
|
|
||||||
private List<String> 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<String, String> 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<String, Object> messageHashMap =
|
|
||||||
(Map<String, Object>) eventOverallWebSocketMessage.getEventMap()
|
|
||||||
.get("message");
|
|
||||||
if (messageHashMap.containsKey("data")) {
|
|
||||||
Map<String, Object> dataHashMap = (Map<String, Object>) messageHashMap.get(
|
|
||||||
"data");
|
|
||||||
if (dataHashMap.containsKey("chat")) {
|
|
||||||
boolean shouldRefreshChat;
|
|
||||||
Map<String, Object> chatMap = (Map<String, Object>) dataHashMap.get("chat");
|
|
||||||
if (chatMap.containsKey("refresh")) {
|
|
||||||
shouldRefreshChat = (boolean) chatMap.get("refresh");
|
|
||||||
if (shouldRefreshChat) {
|
|
||||||
HashMap<String, String> 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<HashMap<String, Object>> joinEventMap =
|
|
||||||
(List<HashMap<String, Object>>) eventOverallWebSocketMessage.getEventMap()
|
|
||||||
.get("join");
|
|
||||||
HashMap<String, Object> internalHashMap;
|
|
||||||
Participant participant;
|
|
||||||
for (int i = 0; i < joinEventMap.size(); i++) {
|
|
||||||
internalHashMap = joinEventMap.get(i);
|
|
||||||
HashMap<String, Object> userMap =
|
|
||||||
(HashMap<String, Object>) 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<String, String> refreshChatHashMap = new HashMap<>();
|
|
||||||
HashMap<String, Object> updateEventMap =
|
|
||||||
(HashMap<String, Object>) 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<String, String> 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<String, String> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,530 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Mario Danic
|
||||||
|
* Copyright (C) 2017-2018 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<String?, Participant>
|
||||||
|
private var messagesQueue: MutableList<String> =
|
||||||
|
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<String, String?>()
|
||||||
|
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<String, Any>?
|
||||||
|
if (messageHashMap!!.containsKey("data")) {
|
||||||
|
val dataHashMap =
|
||||||
|
messageHashMap["data"] as Map<String, Any>?
|
||||||
|
if (dataHashMap!!.containsKey("chat")) {
|
||||||
|
val shouldRefreshChat: Boolean
|
||||||
|
val chatMap =
|
||||||
|
dataHashMap["chat"] as Map<String, Any>?
|
||||||
|
if (chatMap!!.containsKey("refresh")) {
|
||||||
|
shouldRefreshChat = chatMap["refresh"] as Boolean
|
||||||
|
if (shouldRefreshChat) {
|
||||||
|
val refreshChatHashMap =
|
||||||
|
HashMap<String, String?>()
|
||||||
|
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<HashMap<String, Any>>?
|
||||||
|
var internalHashMap: HashMap<String, Any>
|
||||||
|
var participant: Participant
|
||||||
|
var i = 0
|
||||||
|
while (i < joinEventMap!!.size) {
|
||||||
|
internalHashMap = joinEventMap[i]
|
||||||
|
val userMap =
|
||||||
|
internalHashMap["user"] as HashMap<String, Any>?
|
||||||
|
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<String, String?>()
|
||||||
|
val updateEventMap =
|
||||||
|
eventOverallWebSocketMessage.eventMap["update"] as HashMap<String, Any>?
|
||||||
|
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<String, String>()
|
||||||
|
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<String, String?>()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# 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.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
Loading…
Reference in New Issue
Block a user