blob: 89d6746e6f9aaa2938140d74c7e1416006dbec51 [file] [log] [blame]
Hadrien De Sousaccc947d2017-04-12 14:26:52 -04001/*
Adrien Béraude552f692020-11-03 14:30:52 -05002 * Copyright (C) 2004-2020 Savoir-faire Linux Inc.
Hadrien De Sousaccc947d2017-04-12 14:26:52 -04003 *
4 * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
Adrien Béraudace37a22018-06-22 14:58:36 -04005 * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
Hadrien De Sousaccc947d2017-04-12 14:26:52 -04006 *
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, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21package cx.ring.call;
22
Adrien Béraude4f901a2019-10-25 17:26:30 -040023import java.util.ArrayList;
24import java.util.List;
25import java.util.Objects;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040026import java.util.concurrent.TimeUnit;
27
28import javax.inject.Inject;
Adrien Béraudace37a22018-06-22 14:58:36 -040029import javax.inject.Named;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040030
Adrien Béraude4f901a2019-10-25 17:26:30 -040031import cx.ring.facades.ConversationFacade;
32import cx.ring.model.Conference;
33import cx.ring.model.Conversation;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040034import cx.ring.model.SipCall;
Adrien Béraude4f901a2019-10-25 17:26:30 -040035import cx.ring.model.Uri;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040036import cx.ring.mvp.RootPresenter;
37import cx.ring.services.AccountService;
38import cx.ring.services.CallService;
Adrien Béraud8d710752019-02-20 13:53:59 -050039import cx.ring.services.ContactService;
Rayan Osseiran38206c62019-05-15 14:49:03 -040040import cx.ring.services.DeviceRuntimeService;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040041import cx.ring.services.HardwareService;
Amirhossein7bb77aa2020-12-18 16:30:44 -050042import cx.ring.services.PreferencesService;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040043import cx.ring.utils.Log;
Hadrien De Sousac165e9b2017-11-24 12:33:32 -050044import cx.ring.utils.StringUtils;
Adrien Béraude4f901a2019-10-25 17:26:30 -040045import io.reactivex.Maybe;
Rayan Osseiraned9feeb2019-07-05 16:04:30 -040046import io.reactivex.Observable;
Adrien Béraude4f901a2019-10-25 17:26:30 -040047import io.reactivex.Observer;
Adrien Béraudace37a22018-06-22 14:58:36 -040048import io.reactivex.Scheduler;
Adrien Béraud3aeed5d2018-07-26 11:26:13 -040049import io.reactivex.disposables.Disposable;
Adrien Béraude4f901a2019-10-25 17:26:30 -040050import io.reactivex.subjects.BehaviorSubject;
51import io.reactivex.subjects.Subject;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040052
AGS5ef482ce2020-06-12 13:28:51 -040053import static cx.ring.daemon.Ringservice.listCallMediaHandlers;
AGS5ef482ce2020-06-12 13:28:51 -040054
Adrien Béraudace37a22018-06-22 14:58:36 -040055public class CallPresenter extends RootPresenter<CallView> {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040056
57 public final static String TAG = CallPresenter.class.getSimpleName();
58
Adrien Béraudace37a22018-06-22 14:58:36 -040059 private AccountService mAccountService;
Adrien Béraud8d710752019-02-20 13:53:59 -050060 private ContactService mContactService;
Adrien Béraudace37a22018-06-22 14:58:36 -040061 private HardwareService mHardwareService;
62 private CallService mCallService;
Rayan Osseiran38206c62019-05-15 14:49:03 -040063 private DeviceRuntimeService mDeviceRuntimeService;
Adrien Béraude4f901a2019-10-25 17:26:30 -040064 private ConversationFacade mConversationFacade;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040065
Adrien Béraudac3de5a2019-11-06 11:36:01 -050066 private Conference mConference;
Adrien Béraude4f901a2019-10-25 17:26:30 -040067 private final List<SipCall> mPendingCalls = new ArrayList<>();
68 private final Subject<List<SipCall>> mPendingSubject = BehaviorSubject.createDefault(mPendingCalls);
Amirhossein7bb77aa2020-12-18 16:30:44 -050069 private final PreferencesService mPreferencesService;
Adrien Béraude4f901a2019-10-25 17:26:30 -040070
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040071 private boolean mOnGoingCall = false;
Hadrien De Sousa203164c2017-12-05 17:41:42 -050072 private boolean mAudioOnly = true;
Adrien Béraud335d32e2019-05-31 09:31:40 -040073 private boolean permissionChanged = false;
Rayan Osseiranc1532d42019-06-05 13:17:53 -040074 private boolean pipIsActive = false;
Rayan Osseiran3dd04662019-06-28 16:47:48 -040075 private boolean incomingIsFullIntent = true;
Rayan Osseiraned9feeb2019-07-05 16:04:30 -040076 private boolean callInitialized = false;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040077
78 private int videoWidth = -1;
79 private int videoHeight = -1;
80 private int previewWidth = -1;
81 private int previewHeight = -1;
Adrien Béraude4f901a2019-10-25 17:26:30 -040082 private String currentSurfaceId = null;
AGS5ef482ce2020-06-12 13:28:51 -040083 private String currentPluginSurfaceId = null;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040084
Adrien Béraud3aeed5d2018-07-26 11:26:13 -040085 private Disposable timeUpdateTask = null;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040086
87 @Inject
Adrien Béraudace37a22018-06-22 14:58:36 -040088 @Named("UiScheduler")
89 protected Scheduler mUiScheduler;
90
91 @Inject
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040092 public CallPresenter(AccountService accountService,
Adrien Béraud8d710752019-02-20 13:53:59 -050093 ContactService contactService,
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040094 HardwareService hardwareService,
Adrien Béraude4f901a2019-10-25 17:26:30 -040095 CallService callService,
96 DeviceRuntimeService deviceRuntimeService,
Amirhossein7bb77aa2020-12-18 16:30:44 -050097 ConversationFacade conversationFacade,
98 PreferencesService preferencesService) {
Adrien Béraudace37a22018-06-22 14:58:36 -040099 mAccountService = accountService;
Adrien Béraud8d710752019-02-20 13:53:59 -0500100 mContactService = contactService;
Adrien Béraudace37a22018-06-22 14:58:36 -0400101 mHardwareService = hardwareService;
102 mCallService = callService;
Rayan Osseiran38206c62019-05-15 14:49:03 -0400103 mDeviceRuntimeService = deviceRuntimeService;
Adrien Béraude4f901a2019-10-25 17:26:30 -0400104 mConversationFacade = conversationFacade;
Amirhossein7bb77aa2020-12-18 16:30:44 -0500105 mPreferencesService = preferencesService;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400106 }
107
Rayan Osseiran38206c62019-05-15 14:49:03 -0400108 public void cameraPermissionChanged(boolean isGranted) {
109 if (isGranted && mHardwareService.isVideoAvailable()) {
Adrien Béraud37fb52e2020-10-02 19:24:31 -0400110 mHardwareService.initVideo()
111 .onErrorComplete()
112 .blockingAwait();
Adrien Béraud335d32e2019-05-31 09:31:40 -0400113 permissionChanged = true;
Rayan Osseiran38206c62019-05-15 14:49:03 -0400114 }
115 }
116
117 public void audioPermissionChanged(boolean isGranted) {
Rayan Osseirandb4667e2019-05-31 09:41:54 -0400118 if (isGranted && mHardwareService.hasMicrophone()) {
Adrien Béraud22ec8b82019-07-27 21:53:50 -0400119 mCallService.restartAudioLayer();
Rayan Osseiran38206c62019-05-15 14:49:03 -0400120 }
121 }
122
123
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400124 @Override
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400125 public void unbindView() {
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500126 if (!mAudioOnly) {
Adrien Béraud30532a52018-09-11 15:10:05 -0400127 mHardwareService.endCapture();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400128 }
Adrien Béraud99fd4202018-12-13 13:02:21 -0500129 super.unbindView();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400130 }
131
132 @Override
133 public void bindView(CallView view) {
134 super.bindView(view);
Adrien Béraude4f901a2019-10-25 17:26:30 -0400135 /*mCompositeDisposable.add(mAccountService.getRegisteredNames()
Adrien Béraudace37a22018-06-22 14:58:36 -0400136 .observeOn(mUiScheduler)
137 .subscribe(r -> {
138 if (mSipCall != null && mSipCall.getContact() != null) {
139 getView().updateContactBubble(mSipCall.getContact());
140 }
Adrien Béraude4f901a2019-10-25 17:26:30 -0400141 }));*/
Adrien Béraudace37a22018-06-22 14:58:36 -0400142 mCompositeDisposable.add(mHardwareService.getVideoEvents()
143 .observeOn(mUiScheduler)
144 .subscribe(this::onVideoEvent));
Adrien Béraud34a6fa22019-07-28 16:02:59 -0400145 mCompositeDisposable.add(mHardwareService.getAudioState()
146 .observeOn(mUiScheduler)
147 .subscribe(state -> getView().updateAudioState(state)));
148
Adrien Béraud22ec8b82019-07-27 21:53:50 -0400149 /*mCompositeDisposable.add(mHardwareService
Adrien Béraud057e9a32018-09-30 17:48:55 -0400150 .getBluetoothEvents()
151 .subscribe(event -> {
Rayan Osseiran7f77c8a2019-07-17 16:40:50 -0400152 if (!event.connected && mSipCall == null) {
Adrien Béraud057e9a32018-09-30 17:48:55 -0400153 hangupCall();
154 }
Adrien Béraud22ec8b82019-07-27 21:53:50 -0400155 }));*/
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400156 }
157
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500158 public void initOutGoing(String accountId, String contactRingId, boolean audioOnly) {
Adrien Béraud85683c22018-05-23 11:18:43 -0400159 if (accountId == null || contactRingId == null) {
160 Log.e(TAG, "initOutGoing: null account or contact");
Rayan Osseiran106a4742019-07-18 15:03:55 -0400161 hangupCall();
Adrien Béraud85683c22018-05-23 11:18:43 -0400162 return;
163 }
Adrien Béraud7403f352019-11-22 16:30:55 -0500164 if (!mHardwareService.hasCamera()) {
Adrien Béraudd3bf4e42018-02-14 14:44:41 +0100165 audioOnly = true;
Hadrien De Sousafcf2a992017-12-15 11:08:56 -0500166 }
Adrien Béraudeb4bb002019-03-14 15:52:48 -0400167 //getView().blockScreenRotation();
Adrien Béraudace37a22018-06-22 14:58:36 -0400168
Adrien Béraud34915fb2020-08-24 17:12:03 -0400169 Observable<Conference> callObservable = mCallService
Adrien Béraude4f901a2019-10-25 17:26:30 -0400170 .placeCall(accountId, StringUtils.toNumber(contactRingId), audioOnly)
171 //.map(mCallService::getConference)
172 .flatMapObservable(call -> mCallService.getConfUpdates(call))
Adrien Béraud34915fb2020-08-24 17:12:03 -0400173 .share();
174
175 mCompositeDisposable.add(callObservable
Adrien Béraudace37a22018-06-22 14:58:36 -0400176 .observeOn(mUiScheduler)
Adrien Béraude4f901a2019-10-25 17:26:30 -0400177 .subscribe(conference -> {
178 contactUpdate(conference);
179 confUpdate(conference);
Rayan Osseirandb4667e2019-05-31 09:41:54 -0400180 }, e -> {
181 hangupCall();
182 Log.e(TAG, "Error with initOutgoing: " + e.getMessage());
183 }));
Adrien Béraud34915fb2020-08-24 17:12:03 -0400184
185 showConference(callObservable);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400186 }
187
Rayan Osseiraned9feeb2019-07-05 16:04:30 -0400188 /**
189 * Returns to or starts an incoming call
190 *
191 * @param confId the call id
192 * @param actionViewOnly true if only returning to call or if using full screen intent
193 */
194 public void initIncomingCall(String confId, boolean actionViewOnly) {
195 //getView().blockScreenRotation();
196
197 // if the call is incoming through a full intent, this allows the incoming call to display
198 incomingIsFullIntent = actionViewOnly;
199
Adrien Béraude4f901a2019-10-25 17:26:30 -0400200 Observable<Conference> callObservable = mCallService.getConfUpdates(confId)
201 .observeOn(mUiScheduler)
202 .share();
Rayan Osseiraned9feeb2019-07-05 16:04:30 -0400203
204 // Handles the case where the call has been accepted, emits a single so as to only check for permissions and start the call once
Adrien Béraude4f901a2019-10-25 17:26:30 -0400205 mCompositeDisposable.add(callObservable
206 .firstOrError()
207 .subscribe(call -> {
208 if (!actionViewOnly) {
209 contactUpdate(call);
210 confUpdate(call);
211 callInitialized = true;
212 getView().prepareCall(true);
213 }
214 }, e -> {
215 hangupCall();
216 Log.e(TAG, "Error with initIncoming, preparing call flow :" , e);
217 }));
Rayan Osseiraned9feeb2019-07-05 16:04:30 -0400218
219 // Handles retrieving call updates. Items emitted are only used if call is already in process or if user is returning to a call.
Adrien Béraude4f901a2019-10-25 17:26:30 -0400220 mCompositeDisposable.add(callObservable
221 .subscribe(call -> {
222 if (callInitialized || actionViewOnly) {
223 contactUpdate(call);
224 confUpdate(call);
225 }
226 }, e -> {
227 hangupCall();
228 Log.e(TAG, "Error with initIncoming, action view flow: ", e);
229 }));
Adrien Béraud34915fb2020-08-24 17:12:03 -0400230
231 showConference(callObservable);
232 }
233
234 private void showConference(Observable<Conference> conference) {
235 mCompositeDisposable.add(conference
236 .distinctUntilChanged()
237 .switchMap(Conference::getParticipantInfo)
238 .observeOn(mUiScheduler)
239 .subscribe(info -> getView().updateConfInfo(info),
240 e -> Log.e(TAG, "Error with initIncoming, action view flow: ", e)));
Rayan Osseiran3dd04662019-06-28 16:47:48 -0400241 }
242
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400243 public void prepareOptionMenu() {
244 boolean isSpeakerOn = mHardwareService.isSpeakerPhoneOn();
Adrien Béraude4f901a2019-10-25 17:26:30 -0400245 //boolean hasContact = mSipCall != null && null != mSipCall.getContact() && mSipCall.getContact().isUnknown();
Amirhossein128eca42020-11-25 15:03:36 -0500246 boolean canDial = mOnGoingCall && mConference != null;
ayounesc6c2c012019-11-11 11:09:51 -0500247 // get the preferences
248 boolean displayPluginsButton = getView().displayPluginsButton();
249 boolean showPluginBtn = displayPluginsButton && mOnGoingCall && mConference != null;
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500250 boolean hasMultipleCamera = mHardwareService.getCameraCount() > 1 && mOnGoingCall && !mAudioOnly;
ayounes47e93992019-10-28 12:09:53 -0400251 getView().initMenu(isSpeakerOn, hasMultipleCamera, canDial, showPluginBtn, mOnGoingCall);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400252 }
253
254 public void chatClick() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500255 if (mConference == null || mConference.getParticipants().isEmpty()) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400256 return;
257 }
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500258 SipCall firstCall = mConference.getParticipants().get(0);
Adrien Béraude4f901a2019-10-25 17:26:30 -0400259 if (firstCall == null
260 || firstCall.getContact() == null
261 || firstCall.getContact().getIds() == null
262 || firstCall.getContact().getIds().isEmpty()) {
263 return;
264 }
265 getView().goToConversation(firstCall.getAccount(), firstCall.getContact().getIds().get(0));
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400266 }
267
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400268 public void speakerClick(boolean checked) {
269 mHardwareService.toggleSpeakerphone(checked);
270 }
271
272 public void muteMicrophoneToggled(boolean checked) {
273 mCallService.setMuted(checked);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400274 }
275
Rayan Osseiran38206c62019-05-15 14:49:03 -0400276
Adrien Béraudec19fad2018-12-30 16:51:05 -0500277 public boolean isMicrophoneMuted() {
278 return mCallService.isCaptureMuted();
279 }
280
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400281 public void switchVideoInputClick() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500282 if(mConference == null)
Rayan Osseiranca0436c2019-07-16 10:11:06 -0400283 return;
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500284 mHardwareService.switchInput(mConference.getId(), false);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400285 getView().switchCameraIcon(mHardwareService.isPreviewFromFrontCamera());
286 }
287
Adrien Béraudeb4bb002019-03-14 15:52:48 -0400288 public void configurationChanged(int rotation) {
289 mHardwareService.setDeviceOrientation(rotation);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400290 }
291
292 public void dialpadClick() {
293 getView().displayDialPadKeyboard();
294 }
295
296 public void acceptCall() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500297 if (mConference == null) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400298 return;
299 }
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500300 mCallService.accept(mConference.getId());
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400301 }
302
303 public void hangupCall() {
AGS5ef482ce2020-06-12 13:28:51 -0400304 List<String> callMediaHandlers = listCallMediaHandlers();
305
306 for (String callMediaHandler : callMediaHandlers)
307 {
308 toggleCallMediaHandler(callMediaHandler, false);
309 }
310
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500311 if (mConference != null) {
312 if (mConference.isConference())
313 mCallService.hangUpConference(mConference.getId());
Adrien Béraude4f901a2019-10-25 17:26:30 -0400314 else
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500315 mCallService.hangUp(mConference.getId());
Adrien Béraude4f901a2019-10-25 17:26:30 -0400316 }
317 for (SipCall call : mPendingCalls) {
318 mCallService.hangUp(call.getDaemonIdString());
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400319 }
320 finish();
321 }
322
323 public void refuseCall() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500324 final Conference call = mConference;
Adrien Béraudace37a22018-06-22 14:58:36 -0400325 if (call != null) {
Adrien Béraude4f901a2019-10-25 17:26:30 -0400326 mCallService.refuse(call.getId());
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400327 }
328 finish();
329 }
330
331 public void videoSurfaceCreated(Object holder) {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500332 if (mConference == null) {
Alexandre Lisione4d40262016-12-12 14:30:09 -0500333 return;
334 }
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500335 String newId = mConference.getId();
Adrien Béraude4f901a2019-10-25 17:26:30 -0400336 if (!newId.equals(currentSurfaceId)) {
337 mHardwareService.removeVideoSurface(currentSurfaceId);
338 currentSurfaceId = newId;
339 }
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500340 mHardwareService.addVideoSurface(mConference.getId(), holder);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400341 getView().displayContactBubble(false);
342 }
343
Adrien Béraude4f901a2019-10-25 17:26:30 -0400344 public void videoSurfaceUpdateId(String newId) {
345 if (!Objects.equals(newId, currentSurfaceId)) {
346 mHardwareService.updateVideoSurfaceId(currentSurfaceId, newId);
347 currentSurfaceId = newId;
348 }
349 }
350
AGS5ef482ce2020-06-12 13:28:51 -0400351 public void pluginSurfaceCreated(Object holder) {
352 if (mConference == null) {
353 return;
354 }
355 String newId = mConference.getPluginId();
356 if (!newId.equals(currentPluginSurfaceId)) {
357 mHardwareService.removeVideoSurface(currentPluginSurfaceId);
358 currentPluginSurfaceId = newId;
359 }
360 mHardwareService.addVideoSurface(mConference.getPluginId(), holder);
361 getView().displayContactBubble(false);
362 }
363
364 public void pluginSurfaceUpdateId(String newId) {
365 if (!Objects.equals(newId, currentPluginSurfaceId)) {
366 mHardwareService.updateVideoSurfaceId(currentPluginSurfaceId, newId);
367 currentPluginSurfaceId = newId;
368 }
369 }
370
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400371 public void previewVideoSurfaceCreated(Object holder) {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500372 mHardwareService.addPreviewVideoSurface(holder, mConference);
Adrien Béraud30532a52018-09-11 15:10:05 -0400373 //mHardwareService.startCapture(null);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400374 }
375
376 public void videoSurfaceDestroyed() {
Adrien Béraude4f901a2019-10-25 17:26:30 -0400377 if (currentSurfaceId != null) {
378 mHardwareService.removeVideoSurface(currentSurfaceId);
379 currentSurfaceId = null;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400380 }
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400381 }
AGS5ef482ce2020-06-12 13:28:51 -0400382 public void pluginSurfaceDestroyed() {
383 if (currentPluginSurfaceId != null) {
384 mHardwareService.removeVideoSurface(currentPluginSurfaceId);
385 currentPluginSurfaceId = null;
386 }
387 }
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400388 public void previewVideoSurfaceDestroyed() {
389 mHardwareService.removePreviewVideoSurface();
Adrien Béraud30532a52018-09-11 15:10:05 -0400390 mHardwareService.endCapture();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400391 }
392
393 public void displayChanged() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500394 mHardwareService.switchInput(mConference.getId(), false);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400395 }
396
397 public void layoutChanged() {
Adrien Béraudde0d4522018-12-12 12:11:02 -0500398 //getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400399 }
400
Rayan Osseiran38206c62019-05-15 14:49:03 -0400401
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400402 public void uiVisibilityChanged(boolean displayed) {
Adrien Béraud665faa22018-07-26 16:51:20 -0400403 CallView view = getView();
404 if (view != null)
405 view.displayHangupButton(mOnGoingCall && displayed);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400406 }
407
408 private void finish() {
Adrien Béraud3aeed5d2018-07-26 11:26:13 -0400409 if (timeUpdateTask != null && !timeUpdateTask.isDisposed()) {
410 timeUpdateTask.dispose();
411 timeUpdateTask = null;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400412 }
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500413 mConference = null;
Adrien Béraud8d6baf32018-08-03 23:37:31 -0400414 CallView view = getView();
415 if (view != null)
416 view.finish();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400417 }
418
Adrien Béraude4f901a2019-10-25 17:26:30 -0400419 private Disposable contactDisposable = null;
420
421 private void contactUpdate(final Conference conference) {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500422 if (mConference != conference) {
423 mConference = conference;
Adrien Béraude4f901a2019-10-25 17:26:30 -0400424 if (contactDisposable != null && !contactDisposable.isDisposed()) {
425 contactDisposable.dispose();
426 }
427 if (conference.getParticipants().isEmpty())
428 return;
429
430 // Updates of participant (and pending participant) list
431 Observable<List<SipCall>> callsObservable = mPendingSubject
432 .map(pendingList -> {
433 Log.w(TAG, "mPendingSubject onNext " + pendingList.size() + " " + conference.getParticipants().size());
434 if (pendingList.isEmpty())
435 return conference.getParticipants();
436 List<SipCall> newList = new ArrayList<>(conference.getParticipants().size() + pendingList.size());
437 newList.addAll(conference.getParticipants());
438 newList.addAll(pendingList);
439 return newList;
440 });
441
442 // Updates of individual contacts
443 Observable<List<Observable<SipCall>>> contactsObservable = callsObservable
444 .flatMapSingle(calls -> Observable
445 .fromIterable(calls)
Adrien Béraud2d8a8a82020-10-15 11:05:38 -0400446 .map(call -> mContactService.observeContact(call.getAccount(), call.getContact(), false)
Adrien Béraude4f901a2019-10-25 17:26:30 -0400447 .map(contact -> call))
448 .toList(calls.size()));
449
450 // Combined updates of contacts as participant list updates
451 Observable<List<SipCall>> contactUpdates = contactsObservable
452 .switchMap(list -> Observable
453 .combineLatest(list, objects -> {
454 Log.w(TAG, "flatMapObservable " + objects.length);
455 ArrayList<SipCall> calls = new ArrayList<>(objects.length);
456 for (Object call : objects)
457 calls.add((SipCall)call);
458 return (List<SipCall>)calls;
459 }))
460 .filter(list -> !list.isEmpty());
461
462 contactDisposable = contactUpdates
Adrien Béraud2c378b32018-07-11 15:24:02 -0400463 .observeOn(mUiScheduler)
Adrien Béraude4f901a2019-10-25 17:26:30 -0400464 .subscribe(cs -> getView().updateContactBubble(cs), e -> Log.e(TAG, "Error updating contact data", e));
465 mCompositeDisposable.add(contactDisposable);
Adrien Béraud2c378b32018-07-11 15:24:02 -0400466 }
Adrien Béraud7c45b742019-11-15 14:30:11 -0500467 mPendingSubject.onNext(mPendingCalls);
Adrien Béraud2c378b32018-07-11 15:24:02 -0400468 }
469
Adrien Béraude4f901a2019-10-25 17:26:30 -0400470 private void confUpdate(Conference call) {
471 Log.w(TAG, "confUpdate " + call.getId());
472
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500473 mConference = call;
Amirhossein063a5e22020-06-24 17:53:11 -0400474 SipCall.CallStatus status = mConference.getState();
475 if (status == SipCall.CallStatus.HOLD && mCallService.getConferenceList().size() == 1) {
476 mCallService.unhold(mConference.getId());
477 }
Adrien Béraude4f901a2019-10-25 17:26:30 -0400478 mAudioOnly = !call.hasVideo();
Adrien Béraudace37a22018-06-22 14:58:36 -0400479 CallView view = getView();
Adrien Béraud99fd4202018-12-13 13:02:21 -0500480 if (view == null)
481 return;
482 view.updateMenu();
483 if (call.isOnGoing()) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400484 mOnGoingCall = true;
Adrien Béraud34a6fa22019-07-28 16:02:59 -0400485 view.initNormalStateDisplay(mAudioOnly, isMicrophoneMuted());
Adrien Béraudace37a22018-06-22 14:58:36 -0400486 view.updateMenu();
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500487 if (!mAudioOnly) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400488 mHardwareService.setPreviewSettings();
Adrien Béraud7403f352019-11-22 16:30:55 -0500489 mHardwareService.updatePreviewVideoSurface(mConference);
Adrien Béraude4f901a2019-10-25 17:26:30 -0400490 videoSurfaceUpdateId(call.getId());
AGS5ef482ce2020-06-12 13:28:51 -0400491 pluginSurfaceUpdateId(call.getPluginId());
Rayan Osseiranc1532d42019-06-05 13:17:53 -0400492 view.displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission());
Rayan Osseiran3dd04662019-06-28 16:47:48 -0400493 if (permissionChanged) {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500494 mHardwareService.switchInput(mConference.getId(), permissionChanged);
Adrien Béraud335d32e2019-05-31 09:31:40 -0400495 permissionChanged = false;
Rayan Osseiran38206c62019-05-15 14:49:03 -0400496 }
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400497 }
Adrien Béraud3aeed5d2018-07-26 11:26:13 -0400498 if (timeUpdateTask != null)
499 timeUpdateTask.dispose();
Adrien Béraud99fd4202018-12-13 13:02:21 -0500500 timeUpdateTask = mUiScheduler.schedulePeriodicallyDirect(this::updateTime, 0, 1, TimeUnit.SECONDS);
501 } else if (call.isRinging()) {
Adrien Béraude4f901a2019-10-25 17:26:30 -0400502 SipCall scall = call.getCall();
503
Rayan Osseirande9ac142019-07-02 13:01:32 -0400504 view.handleCallWakelock(mAudioOnly);
Adrien Béraude4f901a2019-10-25 17:26:30 -0400505 if (scall.isIncoming()) {
506 if (mAccountService.getAccount(scall.getAccount()).isAutoanswerEnabled()) {
507 mCallService.accept(scall.getDaemonIdString());
Rayan Osseiran3dd04662019-06-28 16:47:48 -0400508 // only display the incoming call screen if the notification is a full screen intent
509 } else if (incomingIsFullIntent) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400510 view.initIncomingCallDisplay();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400511 }
512 } else {
513 mOnGoingCall = false;
Adrien Béraude4f901a2019-10-25 17:26:30 -0400514 view.updateCallStatus(scall.getCallStatus());
Adrien Béraudace37a22018-06-22 14:58:36 -0400515 view.initOutGoingCallDisplay();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400516 }
517 } else {
518 finish();
519 }
520 }
521
Adrien Béraud78c547f2020-09-08 16:01:17 -0400522 public void maximizeParticipant(SipCall call) {
523 if (mConference.getMaximizedCall() == call)
524 call = null;
525 mConference.setMaximizedCall(call);
526 if (call != null) {
527 mCallService.setConfMaximizedParticipant(mConference.getConfId(), call.getDaemonIdString());
528 } else {
529 mCallService.setConfGridLayout(mConference.getConfId());
530 }
531 }
532
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400533 private void updateTime() {
Adrien Béraud6325fad2018-08-15 15:34:11 -0400534 CallView view = getView();
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500535 if (view != null && mConference != null) {
536 if (mConference.isOnGoing()) {
537 long start = mConference.getTimestampStart();
Adrien Béraude4f901a2019-10-25 17:26:30 -0400538 if (start != Long.MAX_VALUE) {
539 view.updateTime((System.currentTimeMillis() - start) / 1000);
540 } else {
541 view.updateTime(-1);
542 }
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400543 }
544 }
545 }
546
Adrien Béraudace37a22018-06-22 14:58:36 -0400547 private void onVideoEvent(HardwareService.VideoEvent event) {
Adrien Béraudeb4bb002019-03-14 15:52:48 -0400548 Log.d(TAG, "VIDEO_EVENT: " + event.start + " " + event.callId + " " + event.w + "x" + event.h);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400549
Adrien Béraudace37a22018-06-22 14:58:36 -0400550 if (event.start) {
Rayan Osseiranc1532d42019-06-05 13:17:53 -0400551 getView().displayVideoSurface(true, !isPipMode() && mDeviceRuntimeService.hasVideoPermission());
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500552 } else if (mConference != null && mConference.getId().equals(event.callId)) {
Rayan Osseiranc1532d42019-06-05 13:17:53 -0400553 getView().displayVideoSurface(event.started, event.started && !isPipMode() && mDeviceRuntimeService.hasVideoPermission());
Adrien Béraudace37a22018-06-22 14:58:36 -0400554 if (event.started) {
555 videoWidth = event.w;
556 videoHeight = event.h;
Adrien Béraudeb4bb002019-03-14 15:52:48 -0400557 getView().resetVideoSize(videoWidth, videoHeight);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400558 }
Adrien Béraud30532a52018-09-11 15:10:05 -0400559 } else if (event.callId == null) {
560 if (event.started) {
561 previewWidth = event.w;
562 previewHeight = event.h;
Adrien Béraudeb4bb002019-03-14 15:52:48 -0400563 getView().resetPreviewVideoSize(previewWidth, previewHeight, event.rot);
Adrien Béraud30532a52018-09-11 15:10:05 -0400564 }
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400565 }
AGS5ef482ce2020-06-12 13:28:51 -0400566 if (mConference != null && mConference.getPluginId().equals(event.callId)) {
567 if (event.started) {
568 previewWidth = event.w;
569 previewHeight = event.h;
570 getView().resetPluginPreviewVideoSize(previewWidth, previewHeight, event.rot);
571 }
572 }
Adrien Béraudeb4bb002019-03-14 15:52:48 -0400573 /*if (event.started || event.start) {
Adrien Béraudde0d4522018-12-12 12:11:02 -0500574 getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
Adrien Béraudeb4bb002019-03-14 15:52:48 -0400575 }*/
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400576 }
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500577
578 public void positiveButtonClicked() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500579 if (mConference.isRinging() && mConference.isIncoming()) {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500580 acceptCall();
581 } else {
582 hangupCall();
583 }
584 }
585
586 public void negativeButtonClicked() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500587 if (mConference.isRinging() && mConference.isIncoming()) {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500588 refuseCall();
589 } else {
590 hangupCall();
591 }
592 }
593
594 public void toggleButtonClicked() {
Amirhossein1cbc8df2020-08-14 11:48:30 -0400595 if (mConference != null && !(mConference.isRinging() && mConference.isIncoming())) {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500596 hangupCall();
597 }
598 }
Adrien Béraud79808d02018-02-20 00:16:28 +0100599
Rayan Osseiran38206c62019-05-15 14:49:03 -0400600 public boolean isAudioOnly() {
601 return mAudioOnly;
602 }
603
Adrien Béraud79808d02018-02-20 00:16:28 +0100604 public void requestPipMode() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500605 if (mConference != null && mConference.isOnGoing() && mConference.hasVideo()) {
606 getView().enterPipMode(mConference.getId());
Adrien Béraud79808d02018-02-20 00:16:28 +0100607 }
608 }
609
Rayan Osseiranc1532d42019-06-05 13:17:53 -0400610 public boolean isPipMode() {
611 return pipIsActive;
612 }
613
Adrien Béraud79808d02018-02-20 00:16:28 +0100614 public void pipModeChanged(boolean pip) {
Rayan Osseiranc1532d42019-06-05 13:17:53 -0400615 pipIsActive = pip;
Adrien Béraud79808d02018-02-20 00:16:28 +0100616 if (pip) {
617 getView().displayHangupButton(false);
618 getView().displayPreviewSurface(false);
Rayan Osseiranc1532d42019-06-05 13:17:53 -0400619 getView().displayVideoSurface(true, false);
Adrien Béraud79808d02018-02-20 00:16:28 +0100620 } else {
621 getView().displayPreviewSurface(true);
Rayan Osseiranc1532d42019-06-05 13:17:53 -0400622 getView().displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission());
Adrien Béraud79808d02018-02-20 00:16:28 +0100623 }
624 }
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400625
agsantos4f7b5a12020-07-08 16:18:07 -0400626 public void toggleCallMediaHandler(String id, boolean toggle)
627 {
628 if (mConference != null && mConference.isOnGoing() && mConference.hasVideo()) {
agsantos5f68e512020-10-26 11:48:52 -0400629 getView().toggleCallMediaHandler(id, mConference.getId(), toggle);
agsantos4f7b5a12020-07-08 16:18:07 -0400630 }
631 }
632
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400633 public boolean isSpeakerphoneOn() {
634 return mHardwareService.isSpeakerPhoneOn();
635 }
Adrien Béraudbcbb4d42018-12-08 16:06:39 -0500636
637 public void sendDtmf(CharSequence s) {
638 mCallService.playDtmf(s.toString());
639 }
Adrien Béraude4f901a2019-10-25 17:26:30 -0400640
641 public void addConferenceParticipant(String accountId, String contactId) {
Adrien Béraude4f901a2019-10-25 17:26:30 -0400642 mCompositeDisposable.add(mConversationFacade.startConversation(accountId, new Uri(contactId))
643 .map(Conversation::getCurrentCalls)
Adrien Béraud94150a72019-11-18 16:41:28 -0500644 .subscribe(confs -> {
645 if (confs.isEmpty()) {
Adrien Béraud7c45b742019-11-15 14:30:11 -0500646 final Observer<SipCall> pendingObserver = new Observer<SipCall>() {
Adrien Béraude4f901a2019-10-25 17:26:30 -0400647 private SipCall call = null;
648 @Override
649 public void onSubscribe(Disposable d) {}
650
651 @Override
652 public void onNext(SipCall sipCall) {
Adrien Béraude4f901a2019-10-25 17:26:30 -0400653 if (call == null) {
654 call = sipCall;
655 mPendingCalls.add(sipCall);
656 }
657 mPendingSubject.onNext(mPendingCalls);
658 }
659
660 @Override
661 public void onError(Throwable e) {}
662
663 @Override
664 public void onComplete() {
Adrien Béraude4f901a2019-10-25 17:26:30 -0400665 if (call != null) {
666 mPendingCalls.remove(call);
667 mPendingSubject.onNext(mPendingCalls);
668 call = null;
669 }
670 }
671 };
672
673 // Place new call, join to conference when answered
674 Maybe<SipCall> newCall = mCallService.placeCallObservable(accountId, contactId, mAudioOnly)
675 .doOnEach(pendingObserver)
676 .filter(SipCall::isOnGoing)
677 .firstElement()
678 .delay(1, TimeUnit.SECONDS)
679 .doOnEvent((v, e) -> pendingObserver.onComplete());
Adrien Béraud7c45b742019-11-15 14:30:11 -0500680 mCompositeDisposable.add(newCall.subscribe(call -> {
681 String id = mConference.getId();
Adrien Béraud94150a72019-11-18 16:41:28 -0500682 if (mConference.isConference()) {
Adrien Béraud7c45b742019-11-15 14:30:11 -0500683 mCallService.addParticipant(call.getDaemonIdString(), id);
684 } else {
Adrien Béraud94150a72019-11-18 16:41:28 -0500685 mCallService.joinParticipant(id, call.getDaemonIdString()).subscribe();
Adrien Béraud7c45b742019-11-15 14:30:11 -0500686 }
687 }));
Adrien Béraude4f901a2019-10-25 17:26:30 -0400688 } else {
689 // Selected contact already in call or conference, join it to current conference
Adrien Béraud94150a72019-11-18 16:41:28 -0500690 Conference selectedConf = confs.get(0);
691 if (selectedConf != mConference) {
692 if (mConference.isConference()) {
693 if (selectedConf.isConference())
694 mCallService.joinConference(mConference.getId(), selectedConf.getId());
695 else
696 mCallService.addParticipant(selectedConf.getId(), mConference.getId());
Adrien Béraude4f901a2019-10-25 17:26:30 -0400697 } else {
Adrien Béraud94150a72019-11-18 16:41:28 -0500698 if (selectedConf.isConference())
699 mCallService.addParticipant(mConference.getId(), selectedConf.getId());
700 else
701 mCallService.joinParticipant(mConference.getId(), selectedConf.getId()).subscribe();
Adrien Béraude4f901a2019-10-25 17:26:30 -0400702 }
703 }
704 }
705 }));
706 }
707
708 public void startAddParticipant() {
Adrien Béraudac3de5a2019-11-06 11:36:01 -0500709 getView().startAddParticipant(mConference.getId());
Adrien Béraude4f901a2019-10-25 17:26:30 -0400710 }
711
712 public void hangupParticipant(SipCall call) {
713 mCallService.hangUp(call.getDaemonIdString());
714 }
715
716 public void openParticipantContact(SipCall call) {
717 getView().goToContact(call.getAccount(), call.getContact());
718 }
Adrien Béraudade9bee2019-12-02 17:13:26 -0500719
720 public void stopCapture() {
721 mHardwareService.stopCapture();
722 }
723
Adrien Béraud20bd7232020-03-16 11:33:23 -0400724 public boolean startScreenShare(Object mediaProjection) {
725 return mHardwareService.startScreenShare(mediaProjection);
Adrien Béraudade9bee2019-12-02 17:13:26 -0500726 }
727
728 public void stopScreenShare() {
729 mHardwareService.stopScreenShare();
730 }
Adrien Béraud78c547f2020-09-08 16:01:17 -0400731
732 public boolean isMaximized(SipCall call) {
733 return mConference.getMaximizedCall() == call;
734 }
agsantos4d495c92020-07-10 12:01:36 -0400735
736 public void startPlugin(String mediaHandlerId) {
737 mHardwareService.startMediaHandler(mediaHandlerId);
738 if(mConference == null)
739 return;
740 mHardwareService.switchInput(mConference.getId(), mHardwareService.isPreviewFromFrontCamera());
741 }
742
743 public void stopPlugin() {
744 mHardwareService.stopMediaHandler();
745 if(mConference == null)
746 return;
747 mHardwareService.switchInput(mConference.getId(), mHardwareService.isPreviewFromFrontCamera());
748 }
Amirhossein7bb77aa2020-12-18 16:30:44 -0500749
750 public boolean setBlockRecordStatus(){
751 return mPreferencesService.getSettings().isRecordingBlocked();
752 }
753
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400754}