Hadrien De Sousa | ccc947d | 2017-04-12 14:26:52 -0400 | [diff] [blame^] | 1 | /* |
| 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 | */ |
| 20 | package cx.ring.call; |
| 21 | |
| 22 | import java.lang.ref.WeakReference; |
| 23 | import java.util.HashMap; |
| 24 | import java.util.concurrent.Executors; |
| 25 | import java.util.concurrent.ScheduledExecutorService; |
| 26 | import java.util.concurrent.TimeUnit; |
| 27 | |
| 28 | import javax.inject.Inject; |
| 29 | |
| 30 | import cx.ring.facades.ConversationFacade; |
| 31 | import cx.ring.model.CallContact; |
| 32 | import cx.ring.model.Conference; |
| 33 | import cx.ring.model.ServiceEvent; |
| 34 | import cx.ring.model.SipCall; |
| 35 | import cx.ring.model.Uri; |
| 36 | import cx.ring.mvp.RootPresenter; |
| 37 | import cx.ring.services.AccountService; |
| 38 | import cx.ring.services.CallService; |
| 39 | import cx.ring.services.ContactService; |
| 40 | import cx.ring.services.DeviceRuntimeService; |
| 41 | import cx.ring.services.HardwareService; |
| 42 | import cx.ring.services.HistoryService; |
| 43 | import cx.ring.services.NotificationService; |
| 44 | import cx.ring.utils.BlockchainInputHandler; |
| 45 | import cx.ring.utils.Log; |
| 46 | import cx.ring.utils.Observable; |
| 47 | import cx.ring.utils.Observer; |
| 48 | import cx.ring.utils.Tuple; |
| 49 | |
| 50 | public 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 | } |