MagicWebSocketInstance.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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. magicMap = new MagicMap();
  80. restartWebSocket();
  81. }
  82. @Override
  83. public void onOpen(WebSocket webSocket, Response response) {
  84. try {
  85. if (TextUtils.isEmpty(resumeId)) {
  86. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledHelloModel(conversationUser, webSocketTicket)));
  87. } else {
  88. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledHelloModelForResume(resumeId)));
  89. }
  90. } catch (IOException e) {
  91. Log.e(TAG, "Failed to serialize hello model");
  92. }
  93. }
  94. private void restartWebSocket() {
  95. Request request = new Request.Builder().url(connectionUrl).build();
  96. this.webSocket = okHttpClient.newWebSocket(request, this);
  97. }
  98. @Override
  99. public void onMessage(WebSocket webSocket, String text) {
  100. Log.d(TAG, "Receiving : " + text);
  101. try {
  102. BaseWebSocketMessage baseWebSocketMessage = LoganSquare.parse(text, BaseWebSocketMessage.class);
  103. String messageType = baseWebSocketMessage.getType();
  104. switch (messageType) {
  105. case "hello":
  106. connected = true;
  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. eventBus.post(new WebSocketCommunicationEvent("hello", null));
  113. break;
  114. case "error":
  115. ErrorOverallWebSocketMessage errorOverallWebSocketMessage = LoganSquare.parse(text, ErrorOverallWebSocketMessage.class);
  116. if (("no_such_session").equals(errorOverallWebSocketMessage.getErrorWebSocketMessage().getCode().equals("no_such_session"))) {
  117. resumeId = "";
  118. }
  119. if (!isPermanentlyClosed) {
  120. restartWebSocket();
  121. }
  122. break;
  123. case "room":
  124. JoinedRoomOverallWebSocketMessage joinedRoomOverallWebSocketMessage = LoganSquare.parse(text, JoinedRoomOverallWebSocketMessage.class);
  125. if (joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage().getRoomPropertiesWebSocketMessage() != null) {
  126. HashMap<String, String> joinRoomHashMap = new HashMap<>();
  127. joinRoomHashMap.put("roomToken", joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage().getRoomId());
  128. currentRoomToken = joinedRoomOverallWebSocketMessage.getRoomWebSocketMessage().getRoomId();
  129. eventBus.post(new WebSocketCommunicationEvent("roomJoined", joinRoomHashMap));
  130. }
  131. break;
  132. case "event":
  133. EventOverallWebSocketMessage eventOverallWebSocketMessage = LoganSquare.parse(text, EventOverallWebSocketMessage.class);
  134. if (eventOverallWebSocketMessage.getEventMap() != null) {
  135. String target = (String) eventOverallWebSocketMessage.getEventMap().get("target");
  136. switch (target) {
  137. case "room":
  138. if (eventOverallWebSocketMessage.getType().equals("message")) {
  139. if (eventOverallWebSocketMessage.getEventMap().containsKey("data")) {
  140. Map<String, Object> dataHashMap = (Map<String, Object>) eventOverallWebSocketMessage.getEventMap().get("data");
  141. if (dataHashMap.containsKey("chat")) {
  142. boolean shouldRefreshChat;
  143. Map<String, Object> chatMap = (Map<String, Object>) dataHashMap.get("chat");
  144. if (chatMap.containsKey("refresh")) {
  145. shouldRefreshChat = (boolean) chatMap.get("refresh");
  146. if (shouldRefreshChat) {
  147. HashMap<String, String> refreshChatHashMap = new HashMap<>();
  148. refreshChatHashMap.put("roomToken", (String) eventOverallWebSocketMessage.getEventMap().get("roomid"));
  149. eventBus.post(new WebSocketCommunicationEvent("refreshChat", refreshChatHashMap));
  150. }
  151. }
  152. }
  153. }
  154. } else if (eventOverallWebSocketMessage.getEventMap().get("type").equals("join")) {
  155. List<HashMap<String, Object>> joinEventMap = (List<HashMap<String, Object>>) eventOverallWebSocketMessage.getEventMap().get("join");
  156. HashMap<String, Object> internalHashMap;
  157. for (int i = 0; i < joinEventMap.size(); i++) {
  158. internalHashMap = joinEventMap.get(i);
  159. HashMap<String, Object> userMap = (HashMap<String, Object>) internalHashMap.get("user");
  160. displayNameHashMap.put((String) internalHashMap.get("sessionid"), (String) userMap.get("displayname"));
  161. userIdSesssionHashMap.put((String) internalHashMap.get("userid"), (String) internalHashMap.get("sessionid"));
  162. }
  163. }
  164. break;
  165. case "participants":
  166. if (eventOverallWebSocketMessage.getEventMap().get("type").equals("update")) {
  167. HashMap<String, String> refreshChatHashMap = new HashMap<>();
  168. HashMap<String, Object> updateEventMap = (HashMap<String, Object>) eventOverallWebSocketMessage.getEventMap().get("update");
  169. refreshChatHashMap.put("roomToken", (String) updateEventMap.get("roomid"));
  170. refreshChatHashMap.put("jobId", Integer.toString(magicMap.add(updateEventMap.get("users"))));
  171. eventBus.post(new WebSocketCommunicationEvent("participantsUpdate", refreshChatHashMap));
  172. }
  173. break;
  174. }
  175. }
  176. break;
  177. case "message":
  178. CallOverallWebSocketMessage callOverallWebSocketMessage = LoganSquare.parse(text, CallOverallWebSocketMessage.class);
  179. if (callOverallWebSocketMessage.getCallWebSocketMessage().getNcSignalingMessage().getFrom() != null) {
  180. HashMap<String, String> messageHashMap = new HashMap<>();
  181. messageHashMap.put("jobId", Integer.toString(magicMap.add(callOverallWebSocketMessage.getCallWebSocketMessage().getNcSignalingMessage())));
  182. eventBus.post(new WebSocketCommunicationEvent("signalingMessage", messageHashMap));
  183. }
  184. break;
  185. case "bye":
  186. connected = false;
  187. isPermanentlyClosed = true;
  188. default:
  189. break;
  190. }
  191. } catch (IOException e) {
  192. Log.e(TAG, "Failed to WebSocket message");
  193. }
  194. }
  195. @Override
  196. public void onMessage(WebSocket webSocket, ByteString bytes) {
  197. Log.d(TAG, "Receiving bytes : " + bytes.hex());
  198. }
  199. @Override
  200. public void onClosing(WebSocket webSocket, int code, String reason) {
  201. Log.d(TAG, "Closing : " + code + " / " + reason);
  202. connected = false;
  203. }
  204. @Override
  205. public void onFailure(WebSocket webSocket, Throwable t, Response response) {
  206. Log.d(TAG, "Error : " + t.getMessage());
  207. connected = false;
  208. if (restartCount < 4) {
  209. restartWebSocket();
  210. } else {
  211. isPermanentlyClosed = true;
  212. }
  213. }
  214. public String getSessionId() {
  215. return sessionId;
  216. }
  217. public boolean hasMCU() {
  218. return hasMCU;
  219. }
  220. public void joinRoomWithRoomTokenAndSession(String roomToken, String normalBackendSession) {
  221. if (!roomToken.equals(currentRoomToken)) {
  222. if (isConnected()) {
  223. try {
  224. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession)));
  225. } catch (IOException e) {
  226. Log.e(TAG, "Failed to serialize room overall websocket message");
  227. }
  228. }
  229. } else {
  230. HashMap<String, String> joinRoomHashMap = new HashMap<>();
  231. joinRoomHashMap.put("roomToken", currentRoomToken);
  232. eventBus.post(new WebSocketCommunicationEvent("roomJoined", joinRoomHashMap));
  233. }
  234. }
  235. public void sendCallMessage(NCMessageWrapper ncMessageWrapper) {
  236. if (isConnected()) {
  237. try {
  238. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledCallMessageModel(ncMessageWrapper)));
  239. } catch (IOException e) {
  240. Log.e(TAG, "Failed to serialize signaling message");
  241. }
  242. }
  243. }
  244. public Object getJobWithId(Integer id) {
  245. Object copyJob = magicMap.get(id);
  246. magicMap.remove(id);
  247. return copyJob;
  248. }
  249. public void requestOfferForSessionIdWithType(String sessionIdParam, String roomType) {
  250. if (isConnected()) {
  251. try {
  252. webSocket.send(LoganSquare.serialize(webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType)));
  253. } catch (IOException e) {
  254. Log.e(TAG, "Failed to offer request");
  255. }
  256. }
  257. }
  258. void sendBye() {
  259. if (isConnected()) {
  260. try {
  261. ByeWebSocketMessage byeWebSocketMessage = new ByeWebSocketMessage();
  262. byeWebSocketMessage.setType("bye");
  263. byeWebSocketMessage.setBye(new HashMap<>());
  264. webSocket.send(LoganSquare.serialize(byeWebSocketMessage));
  265. } catch (IOException e) {
  266. Log.e(TAG, "Failed to serialize bye message");
  267. }
  268. }
  269. }
  270. public boolean isConnected() {
  271. return connected;
  272. }
  273. boolean isPermanentlyClosed() {
  274. return isPermanentlyClosed;
  275. }
  276. public String getDisplayNameForSession(String session) {
  277. if (displayNameHashMap.containsKey(session)) {
  278. return displayNameHashMap.get(session);
  279. }
  280. return NextcloudTalkApplication.getSharedApplication().getString(R.string.nc_nick_guest);
  281. }
  282. public String getSessionForUserId(String userId) {
  283. return userIdSesssionHashMap.get(userId);
  284. }
  285. }