blob: e341996796c10e98edd7aa9cc078508e3e221280 [file] [log] [blame]
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -04001/*
Pierre Duchemin2802bfd2018-02-21 16:41:32 -05002 * Copyright (C) 2004-2018 Savoir-faire Linux Inc.
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -04003 *
4 * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
Adrien Béraud64538722018-03-15 17:34:00 -04005 * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -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.conversation;
22
Pierre Duchemine0c3a492018-02-01 16:22:45 -050023import java.io.File;
Adrien Béraud1b8ac9f2018-05-31 14:34:23 -040024import java.io.IOException;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040025
26import javax.inject.Inject;
Adrien Béraudace37a22018-06-22 14:58:36 -040027import javax.inject.Named;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040028
29import cx.ring.daemon.Blob;
30import cx.ring.facades.ConversationFacade;
Adrien Béraud20d42212018-03-02 14:40:38 -050031import cx.ring.model.Account;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040032import cx.ring.model.CallContact;
33import cx.ring.model.Conference;
34import cx.ring.model.Conversation;
Adrien Béraud7c505112018-03-20 16:03:28 -040035import cx.ring.model.DataTransfer;
Hadrien De Sousa6cd2bd22018-01-02 16:11:32 -050036import cx.ring.model.RingError;
Aline Bonnet15fdf0a2017-04-26 17:02:58 -040037import cx.ring.model.SipCall;
Pierre Duchemin1fa61922018-01-18 10:09:02 -050038import cx.ring.model.TrustRequest;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040039import cx.ring.model.Uri;
40import cx.ring.mvp.RootPresenter;
41import cx.ring.services.AccountService;
42import cx.ring.services.ContactService;
Pierre Duchemin4af6b392018-02-02 11:23:56 -050043import cx.ring.services.DeviceRuntimeService;
Hadrien De Sousa45fb21b2017-12-14 11:06:48 -050044import cx.ring.services.HardwareService;
Hadrien De Sousa6cd2bd22018-01-02 16:11:32 -050045import cx.ring.services.VCardService;
Adrien Béraud7c505112018-03-20 16:03:28 -040046import cx.ring.utils.FileUtils;
Hadrien De Sousa0dc83162017-11-10 16:15:18 -050047import cx.ring.utils.Log;
Adrien Béraudace37a22018-06-22 14:58:36 -040048import cx.ring.utils.StringUtils;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040049import cx.ring.utils.VCardUtils;
50import ezvcard.VCard;
Adrien Béraudace37a22018-06-22 14:58:36 -040051import io.reactivex.Scheduler;
Adrien Béraud1b8ac9f2018-05-31 14:34:23 -040052import io.reactivex.Single;
Adrien Béraudace37a22018-06-22 14:58:36 -040053import io.reactivex.disposables.CompositeDisposable;
54import io.reactivex.schedulers.Schedulers;
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -040055import io.reactivex.subjects.BehaviorSubject;
56import io.reactivex.subjects.Subject;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040057
Adrien Béraudace37a22018-06-22 14:58:36 -040058public class ConversationPresenter extends RootPresenter<ConversationView> {
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040059
Hadrien De Sousa0dc83162017-11-10 16:15:18 -050060 private static final String TAG = ConversationPresenter.class.getSimpleName();
Adrien Béraud68f1adb2018-07-18 18:35:21 -040061 private final ContactService mContactService;
Adrien Béraudace37a22018-06-22 14:58:36 -040062 private final AccountService mAccountService;
Adrien Béraudace37a22018-06-22 14:58:36 -040063 private final HardwareService mHardwareService;
64 private final ConversationFacade mConversationFacade;
65 private final VCardService mVCardService;
66 private final DeviceRuntimeService mDeviceRuntimeService;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040067
68 private Conversation mConversation;
Pierre Duchemin1fa61922018-01-18 10:09:02 -050069 private Uri mContactRingId;
Hadrien De Sousa0dc83162017-11-10 16:15:18 -050070 private String mAccountId;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040071
Adrien Béraudace37a22018-06-22 14:58:36 -040072 private CompositeDisposable mConversationDisposable;
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -040073 private final CompositeDisposable mVisibilityDisposable = new CompositeDisposable();
Adrien Béraudace37a22018-06-22 14:58:36 -040074
75 @Inject
76 @Named("UiScheduler")
77 protected Scheduler mUiScheduler;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040078
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -040079 private final Subject<Conversation> mConversationSubject = BehaviorSubject.create();
80
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040081 @Inject
Adrien Béraud64538722018-03-15 17:34:00 -040082 public ConversationPresenter(ContactService contactService,
83 AccountService accountService,
Hadrien De Sousa45fb21b2017-12-14 11:06:48 -050084 HardwareService hardwareService,
Hadrien De Sousa0dc83162017-11-10 16:15:18 -050085 ConversationFacade conversationFacade,
Hadrien De Sousa6cd2bd22018-01-02 16:11:32 -050086 VCardService vCardService,
Adrien Béraud7c505112018-03-20 16:03:28 -040087 DeviceRuntimeService deviceRuntimeService) {
Adrien Béraud64538722018-03-15 17:34:00 -040088 this.mContactService = contactService;
89 this.mAccountService = accountService;
Hadrien De Sousa45fb21b2017-12-14 11:06:48 -050090 this.mHardwareService = hardwareService;
Hadrien De Sousa0dc83162017-11-10 16:15:18 -050091 this.mConversationFacade = conversationFacade;
Hadrien De Sousa6cd2bd22018-01-02 16:11:32 -050092 this.mVCardService = vCardService;
Pierre Duchemin1fa61922018-01-18 10:09:02 -050093 this.mDeviceRuntimeService = deviceRuntimeService;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040094 }
95
96 @Override
Adrien Béraudace37a22018-06-22 14:58:36 -040097 public void bindView(ConversationView view) {
98 super.bindView(view);
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -040099 mCompositeDisposable.add(mVisibilityDisposable);
100 if (mConversationDisposable == null && mConversation != null)
101 initView(mConversation, view);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400102 }
103
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500104 public void init(String contactRingId, String accountId) {
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500105 init(new Uri(contactRingId), accountId);
106 }
107
108 public void init(Uri contactRingId, String accountId) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400109 Log.w(TAG, "init " + contactRingId + " " + accountId);
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500110 mContactRingId = contactRingId;
111 mAccountId = accountId;
Adrien Béraude5ec8952018-07-11 15:14:44 -0400112 Account account = mAccountService.getAccount(accountId);
113 if (account != null)
114 initContact(account, contactRingId, getView());
115 mCompositeDisposable.add(mConversationFacade
116 .startConversation(accountId, contactRingId)
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -0400117 .observeOn(mUiScheduler)
Adrien Béraud56481172018-06-25 12:03:12 -0400118 .subscribe(this::setConversation, e -> getView().goToHome()));
Adrien Béraudace37a22018-06-22 14:58:36 -0400119 }
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500120
Adrien Béraudace37a22018-06-22 14:58:36 -0400121 private void setConversation(final Conversation conversation) {
122 if (conversation == null || mConversation == conversation)
123 return;
124 mConversation = conversation;
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -0400125 mConversationSubject.onNext(conversation);
126 ConversationView view = getView();
127 if (view != null)
128 initView(conversation, view);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400129 }
130
131 public void pause() {
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -0400132 mVisibilityDisposable.clear();
Adrien Béraudace37a22018-06-22 14:58:36 -0400133 if (mConversation != null) {
Adrien Béraud64538722018-03-15 17:34:00 -0400134 mConversation.setVisible(false);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400135 }
136 }
137
138 public void resume() {
Adrien Béraudace37a22018-06-22 14:58:36 -0400139 Log.w(TAG, "resume " + mConversation + " " + mAccountId + " " + mContactRingId);
Adrien Béraud7aa7a9e2018-06-29 19:50:32 -0400140 mVisibilityDisposable.clear();
Adrien Béraude5ec8952018-07-11 15:14:44 -0400141 mVisibilityDisposable.add(mConversationSubject
142 .firstOrError()
143 .subscribe(conversation -> {
144 conversation.setVisible(true);
145 mConversationFacade.readMessages(mAccountService.getAccount(mAccountId), conversation);
146 }));
Adrien Béraudace37a22018-06-22 14:58:36 -0400147 }
148
149 private CallContact initContact(final Account account, final Uri uri, final ConversationView view) {
150 CallContact contact;
151 if (account.isRing()) {
152 String rawId = uri.getRawRingId();
153 contact = account.getContact(rawId);
154 if (contact == null) {
155 contact = account.getContactFromCache(uri);
156 TrustRequest req = account.getRequest(rawId);
157 if (req == null) {
Adrien Béraude5ec8952018-07-11 15:14:44 -0400158 view.switchToUnknownView(contact.getRingUsername());
Adrien Béraudace37a22018-06-22 14:58:36 -0400159 } else {
Adrien Béraude5ec8952018-07-11 15:14:44 -0400160 view.switchToIncomingTrustRequestView(req.getDisplayname());
Adrien Béraudace37a22018-06-22 14:58:36 -0400161 }
162 } else {
Adrien Béraude5ec8952018-07-11 15:14:44 -0400163 view.switchToConversationView();
Adrien Béraudace37a22018-06-22 14:58:36 -0400164 }
165 Log.w(TAG, "initContact " + contact.getUsername());
166 if (contact.getUsername() == null) {
167 mAccountService.lookupAddress(mAccountId, "", rawId);
168 }
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500169 } else {
Adrien Béraudace37a22018-06-22 14:58:36 -0400170 contact = mContactService.findContact(account, uri);
Adrien Béraude5ec8952018-07-11 15:14:44 -0400171 view.switchToConversationView();
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500172 }
Adrien Béraudace37a22018-06-22 14:58:36 -0400173 view.displayContactPhoto(contact.getPhoto());
174 view.displayContactName(contact);
175 return contact;
176 }
177
178 private void initView(final Conversation c, final ConversationView view) {
179 Log.w(TAG, "initView");
180 if (mConversationDisposable == null) {
181 mConversationDisposable = new CompositeDisposable();
182 mCompositeDisposable.add(mConversationDisposable);
183 }
184 mConversationDisposable.clear();
185 view.hideNumberSpinner();
186
187 Account account = mAccountService.getAccount(mAccountId);
Adrien Béraudace37a22018-06-22 14:58:36 -0400188
189 mConversationDisposable.add(c.getSortedHistory()
Adrien Béraudace37a22018-06-22 14:58:36 -0400190 .subscribe(view::refreshView));
Adrien Béraud2c378b32018-07-11 15:24:02 -0400191 mConversationDisposable.add(c.getContact().getUpdates()
Adrien Béraudace37a22018-06-22 14:58:36 -0400192 .observeOn(mUiScheduler)
Adrien Béraud2c378b32018-07-11 15:24:02 -0400193 .subscribe(contact -> initContact(account, mContactRingId, view)));
Adrien Béraudace37a22018-06-22 14:58:36 -0400194 mConversationDisposable.add(c.getNewElements()
195 .observeOn(mUiScheduler)
196 .subscribe(view::addElement));
197 mConversationDisposable.add(c.getUpdatedElements()
198 .observeOn(mUiScheduler)
199 .subscribe(view::updateElement));
200 mConversationDisposable.add(c.getRemovedElements()
201 .observeOn(mUiScheduler)
202 .subscribe(view::removeElement));
203 mConversationDisposable.add(c.getCalls()
204 .observeOn(mUiScheduler)
205 .subscribe(calls -> updateOngoingCallView()));
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400206 }
207
208 public void prepareMenu() {
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400209 }
210
211 public void addContact() {
212 getView().goToAddContact(mConversation.getContact());
213 }
214
215 public void deleteAction() {
216 getView().displayDeleteDialog(mConversation);
217 }
218
219 public void copyToClipboard() {
220 getView().displayCopyToClipboard(mConversation.getContact());
221 }
222
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500223 public void sendTextMessage(String message) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400224 if (StringUtils.isEmpty(message)) {
225 return;
226 }
227 Conference conference = mConversation.getCurrentCall();
228 if (conference == null || !conference.isOnGoing()) {
Adrien Bérauda9220662018-07-27 15:59:47 -0400229 mConversationFacade.sendTextMessage(mAccountId, mConversation, mContactRingId, message).subscribe();
Adrien Béraudace37a22018-06-22 14:58:36 -0400230 } else {
231 mConversationFacade.sendTextMessage(mConversation, conference, message);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400232 }
233 }
234
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500235 public void selectFile() {
236 getView().openFilePicker();
237 }
238
Adrien Béraud64538722018-03-15 17:34:00 -0400239 public void sendFile(File file) {
240 mConversationFacade.sendFile(mAccountId, mContactRingId, file);
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500241 }
242
Adrien Béraud1b8ac9f2018-05-31 14:34:23 -0400243 public void downloadFile(final DataTransfer transfer, final File dest) {
244 mCompositeDisposable.add(
245 Single.fromCallable(() -> {
246 if (!transfer.isComplete())
247 throw new IllegalStateException();
248 File file = getDeviceRuntimeService().getConversationPath(transfer.getPeerId(), transfer.getStoragePath());
249 if (FileUtils.copyFile(file, dest)) {
250 Log.w(TAG, "Copied file to " + dest.getAbsolutePath() + " (" + FileUtils.readableFileSize(file.length()) + ")");
251 return dest;
252 }
253 throw new IOException();
254 })
255 .subscribeOn(Schedulers.io())
256 .observeOn(mUiScheduler)
257 .subscribe(file -> {
258 getView().displayCompletedDownload(transfer, file);
259 }, error -> {
260 Log.e(TAG, "Can't download file " + dest, error);
261 }));
Adrien Béraud7c505112018-03-20 16:03:28 -0400262 }
263
Adrien Béraudfcf457f2018-04-03 17:46:34 -0400264 public void shareFile(DataTransfer file) {
265 File path = getDeviceRuntimeService().getConversationPath(file.getPeerId(), file.getStoragePath());
266 getView().shareFile(path);
267 }
268
Adrien Béraud7c505112018-03-20 16:03:28 -0400269 public void deleteFile(DataTransfer transfer) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400270 mConversationFacade.deleteFile(transfer);
Adrien Béraud7c505112018-03-20 16:03:28 -0400271 }
272
Hadrien De Sousa6cd2bd22018-01-02 16:11:32 -0500273 public void sendTrustRequest() {
Adrien Béraud0acf5a12018-08-02 13:40:40 -0400274 final String accountId = mAccountId;
275 final Uri contactId = mContactRingId;
276 mVCardService.loadSmallVCard(accountId, VCardService.MAX_SIZE_REQUEST).subscribe(vCard -> {
277 mAccountService.sendTrustRequest(accountId, contactId.getRawRingId(), Blob.fromString(VCardUtils.vcardToString(vCard)));
278 CallContact contact = mContactService.findContact(accountId, contactId);
279 if (contact == null) {
280 Log.e(TAG, "sendTrustRequest: not able to find contact");
281 return;
282 }
283 contact.setStatus(CallContact.Status.REQUEST_SENT);
284 });
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400285 }
286
287 public void blockContact() {
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500288 mAccountService.removeContact(mAccountId, mContactRingId.getHost(), true);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400289 getView().goToHome();
290 }
291
292 public void clickOnGoingPane() {
Adrien Beraud4f644382017-07-21 17:01:51 -0400293 Conference conf = mConversation.getCurrentCall();
294 if (conf != null) {
295 getView().goToCallActivity(conf.getId());
296 } else {
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500297 getView().displayOnGoingCallPane(false);
Adrien Beraud4f644382017-07-21 17:01:51 -0400298 }
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400299 }
300
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500301 public void callWithAudioOnly(boolean audioOnly) {
Adrien Béraudd3bf4e42018-02-14 14:44:41 +0100302 if (audioOnly && !mHardwareService.hasMicrophone()) {
Hadrien De Sousa45fb21b2017-12-14 11:06:48 -0500303 getView().displayErrorToast(RingError.NO_MICROPHONE);
304 return;
305 }
306
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400307 Conference conf = mConversation.getCurrentCall();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400308
Adrien Béraud27570272018-04-20 22:53:04 -0400309 if (conf != null && (conf.getParticipants().isEmpty()
310 || conf.getParticipants().get(0).getCallState() == SipCall.State.INACTIVE
Hadrien De Sousaca1b0552017-06-07 12:07:26 -0400311 || conf.getParticipants().get(0).getCallState() == SipCall.State.FAILURE)) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400312 mConversation.removeConference(conf);
313 conf = null;
314 }
315
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400316 if (conf != null) {
317 getView().goToCallActivity(conf.getId());
318 } else {
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500319 getView().goToCallActivityWithResult(mAccountId, mContactRingId.getRawUriString(), audioOnly);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400320 }
321 }
322
323 public void deleteConversation() {
Adrien Béraudace37a22018-06-22 14:58:36 -0400324 mConversationFacade.clearHistory(mAccountId, mContactRingId).subscribe();
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400325 getView().goToHome();
326 }
327
Adrien Béraudace37a22018-06-22 14:58:36 -0400328 private void updateOngoingCallView() {
Adrien Béraud20d42212018-03-02 14:40:38 -0500329 Conference conf = mConversation.getCurrentCall();
330 if (conf != null && conf.getState() != SipCall.State.INACTIVE) {
331 getView().displayOnGoingCallPane(true);
332 } else {
333 getView().displayOnGoingCallPane(false);
334 }
Adrien Béraud64538722018-03-15 17:34:00 -0400335 getView().scrollToEnd();
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400336 }
337
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500338 public void onBlockIncomingContactRequest() {
339 String accountId = mAccountId == null ? mAccountService.getCurrentAccount().getAccountID() : mAccountId;
Adrien Béraudace37a22018-06-22 14:58:36 -0400340 mConversationFacade.discardRequest(accountId, mContactRingId);
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500341 mAccountService.removeContact(accountId, mContactRingId.getHost(), true);
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500342
343 getView().goToHome();
344 }
345
346 public void onRefuseIncomingContactRequest() {
347 String accountId = mAccountId == null ? mAccountService.getCurrentAccount().getAccountID() : mAccountId;
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500348
Adrien Béraudace37a22018-06-22 14:58:36 -0400349 mConversationFacade.discardRequest(accountId, mContactRingId);
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500350 getView().goToHome();
351 }
352
353 public void onAcceptIncomingContactRequest() {
Adrien Béraudace37a22018-06-22 14:58:36 -0400354 mConversationFacade.acceptRequest(mAccountId, mContactRingId);
355 getView().switchToConversationView();
356 }
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500357
Adrien Béraudace37a22018-06-22 14:58:36 -0400358 public void onAddContact() {
359 sendTrustRequest();
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500360 getView().switchToConversationView();
361 }
362
Pierre Duchemin4af6b392018-02-02 11:23:56 -0500363 public DeviceRuntimeService getDeviceRuntimeService() {
364 return mDeviceRuntimeService;
365 }
Pierre Duchemindf6cb5a2018-02-09 18:05:21 -0500366
367 public void noSpaceLeft() {
368 Log.e(TAG, "configureForFileInfoTextMessage: no space left on device");
369 getView().displayErrorToast(RingError.NO_SPACE_LEFT);
370 }
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400371}