MagicWebSocketInstance.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.nextcloud.talk.webrtc;
  21. import android.text.TextUtils;
  22. import android.util.Log;
  23. import com.bluelinelabs.logansquare.LoganSquare;
  24. import com.nextcloud.talk.R;
  25. import com.nextcloud.talk.application.NextcloudTalkApplication;
  26. import com.nextcloud.talk.events.WebSocketCommunicationEvent;
  27. import com.nextcloud.talk.models.database.UserEntity;
  28. import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
  29. import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage;
  30. import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage;
  31. import com.nextcloud.talk.models.json.websocket.CallOverallWebSocketMessage;
  32. import com.nextcloud.talk.models.json.websocket.ErrorOverallWebSocketMessage;
  33. import com.nextcloud.talk.models.json.websocket.EventOverallWebSocketMessage;
  34. import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage;
  35. import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage;
  36. import com.nextcloud.talk.utils.MagicMap;
  37. import org.greenrobot.eventbus.EventBus;
  38. import java.io.IOException;
  39. import java.util.HashMap;
  40. import java.util.List;
  41. import java.util.Map;
  42. import javax.inject.Inject;
  43. import autodagger.AutoInjector;
  44. import okhttp3.OkHttpClient;
  45. import okhttp3.Request;
  46. import okhttp3.Response;
  47. import okhttp3.WebSocket;
  48. import okhttp3.WebSocketListener;
  49. import okio.ByteString;
  50. @AutoInjector(NextcloudTalkApplication.class)
  51. public class MagicWebSocketInstance extends WebSocketListener {
  52. private static final String TAG = "MagicWebSocketInstance";
  53. @Inject
  54. OkHttpClient okHttpClient;
  55. @Inject
  56. EventBus eventBus;
  57. private UserEntity conversationUser;
  58. private String webSocketTicket;
  59. private String resumeId;
  60. private String sessionId;
  61. private boolean hasMCU;
  62. private boolean connected;
  63. private WebSocketConnectionHelper webSocketConnectionHelper;
  64. private WebSocket webSocket;
  65. private MagicMap magicMap;
  66. private String connectionUrl;
  67. private String currentRoomToken;
  68. private boolean isPermanentlyClosed = false;
  69. private int restartCount = 0;
  70. private HashMap<String, String> displayNameHashMap;
  71. private HashMap<String, String> userIdSesssionHashMap;
  72. MagicWebSocketInstance(UserEntity conversationUser, String connectionUrl, String webSocketTicket) {
  73. NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
  74. this.connectionUrl = connectionUrl;
  75. this.conversationUser = conversationUser;
  76. this.webSocketTicket = webSocketTicket;
  77. this.webSocketConnectionHelper = new WebSocketConnectionHelper();
  78. this.displayNameHashMap = new HashMap<>();
  79. this.userIdSesssionHashMap = new HashMap<>();
  80. magicMap = new MagicMap();
  81. restartWebSocket();
  82. }
  83. @Override
  84. public void onOpen(WebSocket webSocket, Response response) {
  85. try {
  86. if (TextUtils.isEmpty(resumeId)) {
  87. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledHelloModel(conversationUser, webSocketTicket)));
  88. } else {
  89. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledHelloModelForResume(resumeId)));
  90. }
  91. } catch (IOException e) {
  92. Log.e(TAG, "Failed to serialize hello model");
  93. }
  94. }
  95. private void restartWebSocket() {
  96. Request request = new Request.Builder().url(connectionUrl).build();
  97. this.webSocket = okHttpClient.newWebSocket(request, this);
  98. }
  99. @Override
  100. public void onMessage(WebSocket webSocket, String text) {
  101. Log.d(TAG, "Receiving : " + text);
  102. try {
  103. BaseWebSocketMessage baseWebSocketMessage = LoganSquare.parse(text, BaseWebSocketMessage.class);
  104. String messageType = baseWebSocketMessage.getType();
  105. switch (messageType) {
  106. case "hello":
  107. restartCount = 0;
  108. HelloResponseOverallWebSocketMessage helloResponseWebSocketMessage = LoganSquare.parse(text, HelloResponseOverallWebSocketMessage.class);
  109. resumeId = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getResumeId();
  110. sessionId = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getSessionId();
  111. hasMCU = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().serverHasMCUSupport();
  112. connected = true;
  113. eventBus.post(new WebSocketCommunicationEvent("hello", null));
  114. break;
  115. case "error":
  116. ErrorOverallWebSocketMessage errorOverallWebSocketMessage = LoganSquare.parse(text, ErrorOverallWebSocketMessage.class);
  117. if (("no_such_session").equals(errorOverallWebSocketMessage.getErrorWebSocketMessage().getCode().equals("no_such_session"))) {
  118. resumeId = "";
  119. }
  120. if (!isPermanentlyClosed) {
  121. restartWebSocket();
  122. }
  123. break;
  124. case "room":
  125. JoinedRoomOverallWebSocketMessage joinedRoomOverallWebSocketMessage = LoganSquare.parse(text, JoinedRoomOverallWebSocketMessage.class);
  126. if (joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage().getRoomPropertiesWebSocketMessage() != null) {
  127. HashMap<String, String> joinRoomHashMap = new HashMap<>();
  128. joinRoomHashMap.put("roomToken", joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage().getRoomId());
  129. currentRoomToken = joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage().getRoomId();
  130. eventBus.post(new WebSocketCommunicationEvent("roomJoined", joinRoomHashMap));
  131. }
  132. break;
  133. case "event":
  134. EventOverallWebSocketMessage eventOverallWebSocketMessage = LoganSquare.parse(text, EventOverallWebSocketMessage.class);
  135. if (eventOverallWebSocketMessage.getEventMap() != null) {
  136. String target = (String) eventOverallWebSocketMessage.getEventMap().get("target");
  137. switch (target) {
  138. case "room":
  139. if (eventOverallWebSocketMessage.getEventMap().get("type").equals("message")) {
  140. if (eventOverallWebSocketMessage.getEventMap().containsKey("data")) {
  141. Map<String, Object> dataHashMap = (Map<String, Object>) eventOverallWebSocketMessage.getEventMap().get("data");
  142. if (dataHashMap.containsKey("chat")) {
  143. boolean shouldRefreshChat;
  144. Map<String, Object> chatMap = (Map<String, Object>) dataHashMap.get("chat");
  145. if (chatMap.containsKey("refresh")) {
  146. shouldRefreshChat = (boolean) chatMap.get("refresh");
  147. if (shouldRefreshChat) {
  148. HashMap<String, String> refreshChatHashMap = new HashMap<>();
  149. refreshChatHashMap.put("roomToken", (String) eventOverallWebSocketMessage.getEventMap().get("roomid"));
  150. eventBus.post(new WebSocketCommunicationEvent("refreshChat", refreshChatHashMap));
  151. }
  152. }
  153. }
  154. }
  155. } else if (eventOverallWebSocketMessage.getEventMap().get("type").equals("join")) {
  156. List<HashMap<String, Object>> joinEventMap = (List<HashMap<String, Object>>) eventOverallWebSocketMessage.getEventMap().get("join");
  157. HashMap<String, Object> internalHashMap;
  158. for (int i = 0; i < joinEventMap.size(); i++) {
  159. internalHashMap = joinEventMap.get(i);
  160. HashMap<String, Object> userMap = (HashMap<String, Object>) internalHashMap.get("user");
  161. displayNameHashMap.put((String) internalHashMap.get("sessionid"), (String) userMap.get("displayname"));
  162. userIdSesssionHashMap.put((String) internalHashMap.get("userid"), (String) internalHashMap.get("sessionid"));
  163. }
  164. }
  165. break;
  166. case "participants":
  167. if (eventOverallWebSocketMessage.getEventMap().get("type").equals("update")) {
  168. HashMap<String, String> refreshChatHashMap = new HashMap<>();
  169. HashMap<String, Object> updateEventMap = (HashMap<String, Object>) eventOverallWebSocketMessage.getEventMap().get("update");
  170. refreshChatHashMap.put("roomToken", (String) updateEventMap.get("roomid"));
  171. refreshChatHashMap.put("jobId", Integer.toString(magicMap.add(updateEventMap.get("users"))));
  172. eventBus.post(new WebSocketCommunicationEvent("participantsUpdate", refreshChatHashMap));
  173. }
  174. break;
  175. }
  176. }
  177. break;
  178. case "message":
  179. CallOverallWebSocketMessage callOverallWebSocketMessage = LoganSquare.parse(text, CallOverallWebSocketMessage.class);
  180. if (callOverallWebSocketMessage.getCallWebSocketMessage().getNcSignalingMessage().getFrom() != null) {
  181. HashMap<String, String> messageHashMap = new HashMap<>();
  182. messageHashMap.put("jobId", Integer.toString(magicMap.add(callOverallWebSocketMessage.getCallWebSocketMessage().getNcSignalingMessage())));
  183. eventBus.post(new WebSocketCommunicationEvent("signalingMessage", messageHashMap));
  184. }
  185. break;
  186. case "bye":
  187. connected = false;
  188. isPermanentlyClosed = true;
  189. default:
  190. break;
  191. }
  192. } catch (IOException e) {
  193. Log.e(TAG, "Failed to WebSocket message");
  194. }
  195. }
  196. @Override
  197. public void onMessage(WebSocket webSocket, ByteString bytes) {
  198. Log.d(TAG, "Receiving bytes : " + bytes.hex());
  199. }
  200. @Override
  201. public void onClosing(WebSocket webSocket, int code, String reason) {
  202. Log.d(TAG, "Closing : " + code + " / " + reason);
  203. connected = false;
  204. }
  205. @Override
  206. public void onFailure(WebSocket webSocket, Throwable t, Response response) {
  207. Log.d(TAG, "Error : " + t.getMessage());
  208. connected = false;
  209. if (restartCount < 4) {
  210. restartWebSocket();
  211. } else {
  212. isPermanentlyClosed = true;
  213. }
  214. }
  215. public String getSessionId() {
  216. return sessionId;
  217. }
  218. public boolean hasMCU() {
  219. return hasMCU;
  220. }
  221. public void joinRoomWithRoomTokenAndSession(String roomToken, String normalBackendSession) {
  222. if (!roomToken.equals(currentRoomToken)) {
  223. if (isConnected()) {
  224. try {
  225. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession)));
  226. } catch (IOException e) {
  227. Log.e(TAG, "Failed to serialize room overall websocket message");
  228. }
  229. }
  230. } else {
  231. HashMap<String, String> joinRoomHashMap = new HashMap<>();
  232. joinRoomHashMap.put("roomToken", currentRoomToken);
  233. eventBus.post(new WebSocketCommunicationEvent("roomJoined", joinRoomHashMap));
  234. }
  235. }
  236. public void sendCallMessage(NCMessageWrapper ncMessageWrapper) {
  237. if (isConnected()) {
  238. try {
  239. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledCallMessageModel(ncMessageWrapper)));
  240. } catch (IOException e) {
  241. Log.e(TAG, "Failed to serialize signaling message");
  242. }
  243. }
  244. }
  245. public Object getJobWithId(Integer id) {
  246. Object copyJob = magicMap.get(id);
  247. magicMap.remove(id);
  248. return copyJob;
  249. }
  250. public void requestOfferForSessionIdWithType(String sessionIdParam, String roomType) {
  251. if (isConnected()) {
  252. try {
  253. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType)));
  254. } catch (IOException e) {
  255. Log.e(TAG, "Failed to offer request");
  256. }
  257. }
  258. }
  259. void sendBye() {
  260. if (isConnected()) {
  261. try {
  262. ByeWebSocketMessage byeWebSocketMessage = new ByeWebSocketMessage();
  263. byeWebSocketMessage.setType("bye");
  264. byeWebSocketMessage.setBye(new HashMap<>());
  265. webSocket.send(LoganSquare.serialize(byeWebSocketMessage));
  266. } catch (IOException e) {
  267. Log.e(TAG, "Failed to serialize bye message");
  268. }
  269. }
  270. }
  271. public boolean isConnected() {
  272. return connected;
  273. }
  274. boolean isPermanentlyClosed() {
  275. return isPermanentlyClosed;
  276. }
  277. public String getDisplayNameForSession(String session) {
  278. if (displayNameHashMap.containsKey(session)) {
  279. return displayNameHashMap.get(session);
  280. }
  281. return NextcloudTalkApplication.getSharedApplication().getString(R.string.nc_nick_guest);
  282. }
  283. public String getSessionForUserId(String userId) {
  284. return userIdSesssionHashMap.get(userId);
  285. }
  286. }