blob: 8f0bfc2520d0df4c539203e60ccfa02b6f200aa9 [file] [log] [blame]
Hadrien De Sousaccc947d2017-04-12 14:26:52 -04001/*
2 * Copyright (C) 2017 Savoir-faire Linux Inc.
3 *
4 * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20package cx.ring.call;
21
22import java.lang.ref.WeakReference;
23import java.util.HashMap;
24import java.util.concurrent.Executors;
25import java.util.concurrent.ScheduledExecutorService;
26import java.util.concurrent.TimeUnit;
27
28import javax.inject.Inject;
29
30import cx.ring.facades.ConversationFacade;
31import cx.ring.model.CallContact;
32import cx.ring.model.Conference;
33import cx.ring.model.ServiceEvent;
34import cx.ring.model.SipCall;
35import cx.ring.model.Uri;
36import cx.ring.mvp.RootPresenter;
37import cx.ring.services.AccountService;
38import cx.ring.services.CallService;
39import cx.ring.services.ContactService;
40import cx.ring.services.DeviceRuntimeService;
41import cx.ring.services.HardwareService;
42import cx.ring.services.HistoryService;
43import cx.ring.services.NotificationService;
44import cx.ring.utils.BlockchainInputHandler;
45import cx.ring.utils.Log;
46import cx.ring.utils.Observable;
47import cx.ring.utils.Observer;
48import cx.ring.utils.Tuple;
49
50public class CallPresenter extends RootPresenter<CallView> implements Observer<ServiceEvent> {
51
52 public final static String TAG = CallPresenter.class.getSimpleName();
53
54 protected AccountService mAccountService;
55 protected ConversationFacade mConversationFacade;
56 protected NotificationService mNotificationService;
57 protected DeviceRuntimeService mDeviceRuntimeService;
58 protected HardwareService mHardwareService;
59 protected CallService mCallService;
60 protected ContactService mContactService;
61 protected HistoryService mHistoryService;
62
63 private SipCall mSipCall;
64 private boolean mOnGoingCall = false;
65 private boolean mHasVideo = false;
66
67 private int videoWidth = -1;
68 private int videoHeight = -1;
69 private int previewWidth = -1;
70 private int previewHeight = -1;
71
72 private BlockchainInputHandler mBlockchainInputHandler;
73
74 private ScheduledExecutorService executor;
75 private Runnable timeRunnable = new Runnable() {
76 public void run() {
77 updateTime();
78 }
79 };
80
81 @Inject
82 public CallPresenter(AccountService accountService,
83 ConversationFacade conversationFacade,
84 NotificationService notificationService,
85 DeviceRuntimeService deviceRuntimeService,
86 HardwareService hardwareService,
87 CallService callService,
88 ContactService contactService,
89 HistoryService mHistoryService) {
90 this.mAccountService = accountService;
91 this.mConversationFacade = conversationFacade;
92 this.mNotificationService = notificationService;
93 this.mDeviceRuntimeService = deviceRuntimeService;
94 this.mHardwareService = hardwareService;
95 this.mCallService = callService;
96 this.mContactService = contactService;
97 this.mHistoryService = mHistoryService;
98 }
99
100 @Override
101 public void afterInjection() {
102
103 }
104
105 @Override
106 public void unbindView() {
107 super.unbindView();
108 mAccountService.removeObserver(this);
109 mCallService.removeObserver(this);
110 mHardwareService.removeObserver(this);
111
112 if (mHasVideo) {
113 mHardwareService.stopCapture();
114 }
115
116 mDeviceRuntimeService.closeAudioState();
117 }
118
119 @Override
120 public void bindView(CallView view) {
121 super.bindView(view);
122 mAccountService.addObserver(this);
123 mCallService.addObserver(this);
124 mHardwareService.addObserver(this);
125 }
126
127 public void initOutGoing(String accountId, Uri number, boolean hasVideo) {
128 CallContact contact = mContactService.findContactByNumber(number.getRawUriString());
129
130 Log.d(TAG, "initOutGoing: " + (contact != null));
131
132 String callId = mCallService.placeCall(accountId, number.getUriString(), hasVideo);
133 if (callId == null || callId.isEmpty()) {
134 finish();
135 return;
136 }
137
138 mSipCall = mCallService.getCurrentCallForId(callId);
139 mSipCall.muteVideo(!hasVideo);
140 mSipCall.setCallID(callId);
141 mSipCall.setContact(contact);
142 mHasVideo = hasVideo;
143 confUpdate();
144 getUsername();
145 getContactDetails();
146 getView().blockScreenRotation();
147 }
148
149 public void initIncoming(String confId) {
150 mSipCall = mCallService.getCurrentCallForId(confId);
151 //FIXME sipCall is null for unknowm reason atm
152 if (mSipCall == null) {
153 finish();
154 return;
155 }
156 CallContact contact = mSipCall.getContact();
157 if (contact == null) {
158 contact = mContactService.findContactByNumber(mSipCall.getNumberUri().getRawUriString());
159 }
160 mSipCall.setContact(contact);
161 mHasVideo = true;
162 confUpdate();
163 getUsername();
164 getContactDetails();
165 getView().blockScreenRotation();
166
167 mDeviceRuntimeService.updateAudioState(mSipCall.isRinging() && mSipCall.isIncoming());
168 }
169
170 public void prepareOptionMenu() {
171 boolean isSpeakerOn = mHardwareService.isSpeakerPhoneOn();
172 boolean hasContact = mSipCall != null && null != mSipCall.getContact() && mSipCall.getContact().isUnknown();
173 boolean canDial = mOnGoingCall && mSipCall != null && !mSipCall.isIncoming();
174 boolean hasMultipleCamera = mHardwareService.getCameraCount() > 1 && mOnGoingCall && mHasVideo;
175 getView().initMenu(isSpeakerOn, hasContact, hasMultipleCamera, canDial, mOnGoingCall);
176 }
177
178 public void chatClick() {
179 if (mSipCall == null
180 || mSipCall.getContact() == null
181 || mSipCall.getContact().getIds() == null
182 || mSipCall.getContact().getIds().isEmpty()) {
183 return;
184 }
185 getView().goToConversation(mSipCall.getContact().getIds().get(0));
186 }
187
188 public void addContact() {
189 if (mSipCall == null || mSipCall.getContact() == null) {
190 return;
191 }
192 getView().goToAddContact(mSipCall.getContact());
193 }
194
195 public void speakerClick() {
196 mHardwareService.switchSpeakerPhone();
197 }
198
199 public void switchVideoInputClick() {
200 mHardwareService.switchInput(mSipCall.getCallId());
201 getView().switchCameraIcon(mHardwareService.isPreviewFromFrontCamera());
202 }
203
204 public void screenRotationClick() {
205 getView().changeScreenRotation();
206 }
207
208 public void configurationChanged() {
209 mHardwareService.restartCamera(mSipCall.getCallId());
210 }
211
212 public void dialpadClick() {
213 getView().displayDialPadKeyboard();
214 }
215
216 public void acceptCall() {
217 if (mSipCall == null) {
218 return;
219 }
220 mCallService.accept(mSipCall.getCallId());
221 }
222
223 public void hangupCall() {
224 if (mSipCall != null) {
225 mCallService.hangUp(mSipCall.getCallId());
226 }
227 finish();
228 }
229
230 public void refuseCall() {
231 if (mSipCall != null) {
232 mCallService.refuse(mSipCall.getCallId());
233 mNotificationService.cancelCallNotification(mSipCall.getCallId().hashCode());
234 }
235 finish();
236 }
237
238 public void videoSurfaceCreated(Object holder) {
239 mHardwareService.addVideoSurface(mSipCall.getCallId(), holder);
240 getView().displayContactBubble(false);
241 }
242
243 public void previewVideoSurfaceCreated(Object holder) {
244 mHardwareService.addPreviewVideoSurface(holder);
245 mHardwareService.startCapture(null);
246 }
247
248 public void videoSurfaceDestroyed() {
249 if (mSipCall == null) {
250 return;
251 }
252 mHardwareService.removeVideoSurface(mSipCall.getCallId());
253 }
254
255 public void previewVideoSurfaceDestroyed() {
256 mHardwareService.removePreviewVideoSurface();
257 mHardwareService.stopCapture();
258 }
259
260 public void displayChanged() {
261 mHardwareService.switchInput(mSipCall.getCallId());
262 }
263
264 public void layoutChanged() {
265 getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
266 }
267
268 public void uiVisibilityChanged(boolean displayed) {
269 getView().displayHangupButton(mOnGoingCall && displayed);
270 }
271
272 private void finish() {
273 if (mSipCall != null) {
274 mNotificationService.cancelCallNotification(mSipCall.getCallId().hashCode());
275 }
276 if (executor != null && !executor.isShutdown()) {
277 executor.shutdown();
278 }
279 mSipCall = null;
280 getView().finish();
281 }
282
283 private void confUpdate() {
284 if (mSipCall == null) {
285 return;
286 }
287 if (mSipCall.isOnGoing()) {
288 mOnGoingCall = true;
289 getView().initNormalStateDisplay(mHasVideo);
290 getView().initContactDisplay(mSipCall);
291 if (mHasVideo) {
292 mHardwareService.setPreviewSettings();
293 getView().displayVideoSurface(true);
294 }
295 executor = Executors.newScheduledThreadPool(1);
296 executor.scheduleAtFixedRate(timeRunnable, 0, 1, TimeUnit.SECONDS);
297 } else if (mSipCall.isRinging()) {
298 if (mSipCall.isIncoming()) {
299 if (mAccountService.getAccount(mSipCall.getAccount()).isAutoanswerEnabled()) {
300 mCallService.accept(mSipCall.getCallId());
301 } else {
302 getView().initIncomingCallDisplay();
303 getView().initContactDisplay(mSipCall);
304 }
305 } else {
306 mOnGoingCall = false;
307 getView().updateCallStatus(mSipCall.getCallState());
308 getView().initOutGoingCallDisplay();
309 getView().initContactDisplay(mSipCall);
310 }
311 } else {
312 finish();
313 }
314 }
315
316 private void updateTime() {
317 if (mSipCall != null) {
318 long duration = System.currentTimeMillis() - mSipCall.getTimestampStart();
319 duration = duration / 1000;
320 if (mSipCall.isOnGoing()) {
321 getView().updateTime(duration);
322 }
323 }
324 }
325
326 private void getUsername() {
327 if (mSipCall == null) {
328 return;
329 }
330
331 if (mBlockchainInputHandler == null || !mBlockchainInputHandler.isAlive()) {
332 mBlockchainInputHandler = new BlockchainInputHandler(new WeakReference<>(mAccountService));
333 }
334
335 String[] split = mSipCall.getNumber().split(":");
336 if (split.length > 0) {
337 mBlockchainInputHandler.enqueueNextLookup(split[1]);
338 }
339 }
340
341 private void getContactDetails() {
342 CallContact callContact = mSipCall.getContact();
343 Tuple<String, byte[]> tuple = mContactService.loadContactData(callContact);
344 getView().updateContactBubbleWithVCard(tuple.first, tuple.second);
345 }
346
347 private void parseCall(String callId, int callState, HashMap<String, String> callDetails) {
348 if (mSipCall == null || !mSipCall.getCallId().equals(callId)) {
349 return;
350 }
351
352 if (callState == SipCall.State.INCOMING ||
353 callState == SipCall.State.OVER) {
354 mHistoryService.updateVCard();
355 }
356
357 int oldState = mSipCall.getCallState();
358
359 if (callState != oldState) {
360 if ((mSipCall.isRinging() || callState == SipCall.State.CURRENT) && mSipCall.getTimestampStart() == 0) {
361 mSipCall.setTimestampStart(System.currentTimeMillis());
362 }
363 if (callState == SipCall.State.RINGING) {
364 mAccountService.sendProfile(callId, mSipCall.getAccount());
365 }
366 mSipCall.setCallState(callState);
367 }
368
369 mSipCall.setDetails(callDetails);
370
371 if (callState == SipCall.State.HUNGUP
372 || callState == SipCall.State.BUSY
373 || callState == SipCall.State.FAILURE
374 || callState == SipCall.State.OVER) {
375 finish();
376 } else if (callState != SipCall.State.INACTIVE) {
377 mNotificationService.showCallNotification(new Conference(mSipCall));
378 }
379
380 mDeviceRuntimeService.updateAudioState(mSipCall.isRinging() && mSipCall.isIncoming());
381 }
382
383
384 @Override
385 public void update(Observable observable, ServiceEvent event) {
386 if (event == null) {
387 return;
388 }
389
390 if (observable instanceof CallService) {
391 switch (event.getEventType()) {
392 case CALL_STATE_CHANGED:
393 String callId = event.getEventInput(ServiceEvent.EventInput.CALL_ID, String.class);
394 int newState = SipCall.stateFromString(event.getEventInput(ServiceEvent.EventInput.STATE, String.class));
395 HashMap<String, String> callDetails = (HashMap<String, String>) event.getEventInput(ServiceEvent.EventInput.DETAILS, HashMap.class);
396
397 Log.d(TAG, "CALL_STATE_CHANGED: " + callId + " " + newState);
398
399 parseCall(callId, newState, callDetails);
400 confUpdate();
401 break;
402 }
403 } else if (observable instanceof AccountService) {
404 switch (event.getEventType()) {
405 case REGISTERED_NAME_FOUND:
406 String name = event.getEventInput(ServiceEvent.EventInput.NAME, String.class);
407 String address = event.getEventInput(ServiceEvent.EventInput.ADDRESS, String.class);
408
409 if (mSipCall.getNumber().contains(address)) {
410 getView().updateContactBubble(name);
411 }
412 break;
413 }
414 } else if (observable instanceof HardwareService) {
415 switch (event.getEventType()) {
416 case VIDEO_EVENT:
417 boolean videoStart = event.getEventInput(ServiceEvent.EventInput.VIDEO_START, Boolean.class, false);
418 boolean camera = event.getEventInput(ServiceEvent.EventInput.VIDEO_CAMERA, Boolean.class, false);
419 String callId = event.getEventInput(ServiceEvent.EventInput.VIDEO_CALL, String.class);
420
421 Log.d(TAG, "VIDEO_EVENT: " + videoStart + " " + camera + " " + callId);
422
423 if (videoStart) {
424 getView().displayVideoSurface(true);
425 } else if (camera) {
426 previewWidth = event.getEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, Integer.class, 0);
427 previewHeight = event.getEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, Integer.class, 0);
428 } else if (mSipCall != null && callId != null && mSipCall.getCallId().equals(callId)) {
429 boolean videoStarted = event.getEventInput(ServiceEvent.EventInput.VIDEO_STARTED, Boolean.class, false);
430 getView().displayVideoSurface(videoStarted);
431 if (videoStarted) {
432 videoWidth = event.getEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, Integer.class, 0);
433 videoHeight = event.getEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, Integer.class, 0);
434 }
435 }
436 getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
437 break;
438 }
439 }
440 }
441}