Commit c31c8f8e authored by Thibault Wittemberg's avatar Thibault Wittemberg Committed by Aline Bonnet
Browse files

mvp/injection: Remove Conversation features from LocalService

Moves conversation features from LocalService to a new ConversationFacade.
It will allow Presenters to only communicate with low level layers.

This commit fix a ConcurrentModificationException
Switch putAll() to put() on textMessages and historyCalls fix the crash
Refactor calls to HistoryService to be asynchronous

Change-Id: I33fba4db759ec66777c3b88573d5901ce2d3b56f
Tuleap: #1367
parent fe13e7cf
......@@ -94,17 +94,9 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo
}
return;
}
int lastPos = mTexts.size();
int newItems = list.size() - lastPos;
if (lastPos == 0 || newItems < 0) {
mTexts.clear();
mTexts.addAll(list);
notifyDataSetChanged();
} else {
for (int i = lastPos; i < list.size(); i++)
mTexts.add(list.get(i));
notifyItemRangeInserted(lastPos, newItems);
}
mTexts.clear();
mTexts.addAll(list);
notifyDataSetChanged();
}
@Override
......
......@@ -47,6 +47,7 @@ import javax.inject.Inject;
import cx.ring.BuildConfig;
import cx.ring.R;
import cx.ring.application.RingApplication;
import cx.ring.facades.ConversationFacade;
import cx.ring.fragments.CallFragment;
import cx.ring.model.CallContact;
import cx.ring.model.Conference;
......@@ -69,6 +70,9 @@ public class CallActivity extends AppCompatActivity implements Callbacks, CallFr
@Inject
AccountService mAccountService;
@Inject
ConversationFacade mConversationFacade;
private boolean init = false;
private View mMainView;
......@@ -240,7 +244,7 @@ public class CallActivity extends AppCompatActivity implements Callbacks, CallFr
mProximityManager.startTracking();
if (mSavedConferenceId != null) {
mDisplayedConference = service.getConference(mSavedConferenceId);
mDisplayedConference = mConversationFacade.getConference(mSavedConferenceId);
} else {
checkExternalCall();
}
......@@ -276,7 +280,7 @@ public class CallActivity extends AppCompatActivity implements Callbacks, CallFr
private Pair<Account, Uri> guess(Uri number, String account_id) {
Account a = mAccountService.getAccount(account_id);
Conversation conv = service.findConversationByNumber(number);
Conversation conv = mConversationFacade.findOrStartConversationByNumber(number);
// Guess account from number
if (a == null && number != null)
......@@ -324,11 +328,11 @@ public class CallActivity extends AppCompatActivity implements Callbacks, CallFr
SipCall call = new SipCall(null, g.first.getAccountID(), g.second, SipCall.Direction.OUTGOING);
call.muteVideo(!hasVideo);
mDisplayedConference = service.placeCall(call);
mDisplayedConference = mConversationFacade.placeCall(call);
} else if (Intent.ACTION_VIEW.equals(action)) {
String conf_id = u.getLastPathSegment();
Log.d(TAG, "conf " + conf_id);
mDisplayedConference = service.getConference(conf_id);
mDisplayedConference = mConversationFacade.getConference(conf_id);
}
return false;
......
......@@ -33,11 +33,13 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import cx.ring.R;
import cx.ring.facades.ConversationFacade;
import cx.ring.fragments.ConversationFragment;
import cx.ring.model.Uri;
import cx.ring.service.IDRingService;
import cx.ring.service.LocalService;
......@@ -46,12 +48,16 @@ public class ConversationActivity extends AppCompatActivity implements LocalServ
@BindView(R.id.main_toolbar)
Toolbar mToolbar;
@Inject
ConversationFacade mConversationFacade;
private static final String TAG = ConversationActivity.class.getSimpleName();
static final long REFRESH_INTERVAL_MS = 30 * 1000;
private boolean mBound = false;
private LocalService mService = null;
private final Handler mRefreshTaskHandler = new Handler();
private ConversationFragment mConversationFragment;
@Override
......@@ -137,7 +143,7 @@ public class ConversationActivity extends AppCompatActivity implements LocalServ
switch (requestCode) {
case ConversationFragment.REQ_ADD_CONTACT:
if (mService != null) {
mService.refreshConversations();
mConversationFacade.refreshConversations();
}
break;
}
......
......@@ -51,6 +51,7 @@ import cx.ring.services.AccountService;
import cx.ring.services.CallService;
import cx.ring.services.ConferenceService;
import cx.ring.services.ContactServiceImpl;
import cx.ring.facades.ConversationFacade;
import cx.ring.services.DaemonService;
import cx.ring.services.DeviceRuntimeServiceImpl;
import cx.ring.services.HardwareService;
......@@ -135,6 +136,8 @@ public interface RingInjectionComponent {
void inject(NotificationServiceImpl service);
void inject(ConversationFacade service);
void inject(BootReceiver receiver);
void inject(AboutPresenter presenter);
......
......@@ -32,6 +32,7 @@ import cx.ring.services.CallService;
import cx.ring.services.ConferenceService;
import cx.ring.services.ContactService;
import cx.ring.services.ContactServiceImpl;
import cx.ring.facades.ConversationFacade;
import cx.ring.services.DaemonService;
import cx.ring.services.DeviceRuntimeService;
import cx.ring.services.DeviceRuntimeServiceImpl;
......@@ -148,6 +149,19 @@ public class ServiceInjectionModule {
return contactService;
}
@Provides
@Singleton
ConversationFacade provideConversationtFacade(
AccountService accountService,
ContactService contactService,
ConferenceService conferenceService,
HistoryService historyService
) {
ConversationFacade conversationFacade = new ConversationFacade(historyService);
mRingApplication.getRingInjectionComponent().inject(conversationFacade);
return conversationFacade;
}
@Provides
@Named("DaemonExecutor")
@Singleton
......
......@@ -80,6 +80,7 @@ import cx.ring.adapters.ContactDetailsTask;
import cx.ring.application.RingApplication;
import cx.ring.client.ConversationActivity;
import cx.ring.client.HomeActivity;
import cx.ring.facades.ConversationFacade;
import cx.ring.interfaces.CallInterface;
import cx.ring.model.CallContact;
import cx.ring.model.Conference;
......@@ -117,6 +118,9 @@ public class CallFragment extends Fragment implements CallInterface, ContactDeta
@Inject
AccountService mAccountService;
@Inject
ConversationFacade mConversationFacade;
@Inject
NotificationService mNotificationService;
......@@ -590,7 +594,7 @@ public class CallFragment extends Fragment implements CallInterface, ContactDeta
if (service == null)
return;
Conference c = service.getConference(getConference().getId());
Conference c = mConversationFacade.getConference(getConference().getId());
mCallbacks.updateDisplayedConference(c);
if (c == null || c.getParticipants().isEmpty()) {
mCallbacks.terminateCall();
......
......@@ -46,6 +46,7 @@ import cx.ring.application.RingApplication;
import cx.ring.client.CallActivity;
import cx.ring.client.ConversationActivity;
import cx.ring.client.HomeActivity;
import cx.ring.facades.ConversationFacade;
import cx.ring.model.Account;
import cx.ring.model.CallContact;
import cx.ring.model.Conference;
......@@ -56,18 +57,18 @@ import cx.ring.model.Uri;
import cx.ring.service.LocalService;
import cx.ring.services.AccountService;
import cx.ring.services.CallService;
import cx.ring.services.ContactService;
import cx.ring.utils.ActionHelper;
import cx.ring.utils.ClipboardHelper;
import cx.ring.utils.ContentUriHandler;
import cx.ring.utils.Observable;
import cx.ring.utils.Observer;
import cx.ring.services.ContactService;
public class ConversationFragment extends Fragment implements
Conversation.ConversationActionCallback,
ClipboardHelper.ClipboardHelperCallback,
ContactDetailsTask.DetailsLoadedCallback,
Observer<ServiceEvent>{
Observer<ServiceEvent> {
@Inject
ContactService mContactService;
......@@ -78,6 +79,9 @@ public class ConversationFragment extends Fragment implements
@Inject
AccountService mAccountService;
@Inject
ConversationFacade mConversationFacade;
@BindView(R.id.msg_input_txt)
EditText mMsgEditTxt;
......@@ -110,7 +114,6 @@ public class ConversationFragment extends Fragment implements
private ConversationAdapter mAdapter = null;
private NumberAdapter mNumberAdapter = null;
public static Boolean isTabletMode(Context context) {
return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
&& context.getResources().getConfiguration().screenWidthDp >= MIN_SIZE_TABLET;
......@@ -125,7 +128,7 @@ public class ConversationFragment extends Fragment implements
Uri number = new Uri(bundle.getString("number"));
Log.d(TAG, "getConversation " + conversationId + " " + number);
Conversation conversation = service.getConversation(conversationId);
Conversation conversation = mConversationFacade.getConversationById(conversationId);
if (conversation == null) {
long contactId = CallContact.contactIdFromId(conversationId);
Log.d(TAG, "no conversation found, contact_id " + contactId);
......@@ -150,7 +153,7 @@ public class ConversationFragment extends Fragment implements
}
}
}
conversation = service.startConversation(contact);
conversation = mConversationFacade.startConversation(contact);
}
Log.d(TAG, "returning " + conversation.getContact().getDisplayName() + " " + number);
......@@ -307,7 +310,7 @@ public class ConversationFragment extends Fragment implements
if (mVisible && mConversation != null && !mConversation.isVisible()) {
mConversation.setVisible(true);
service.readConversation(mConversation);
mConversationFacade.readConversation(mConversation);
}
if (mDeleteConversation) {
......@@ -351,8 +354,8 @@ public class ConversationFragment extends Fragment implements
super.onPause();
Log.d(TAG, "onPause");
mVisible = false;
if (mConversation != null && mCallbacks.getService() != null) {
mCallbacks.getService().readConversation(mConversation);
if (mConversation != null ) {
mConversationFacade.readConversation(mConversation);
mConversation.setVisible(false);
}
......@@ -367,9 +370,7 @@ public class ConversationFragment extends Fragment implements
mVisible = true;
if (mConversation != null) {
mConversation.setVisible(true);
if (mCallbacks.getService() != null) {
mCallbacks.getService().readConversation(mConversation);
}
mConversationFacade.readConversation(mConversation);
}
IntentFilter filter = new IntentFilter(LocalService.ACTION_CONF_UPDATE);
......@@ -508,9 +509,9 @@ public class ConversationFragment extends Fragment implements
if (guess == null || guess.first == null) {
return;
}
mCallbacks.getService().sendTextMessage(guess.first.getAccountID(), guess.second, txt);
mConversationFacade.sendTextMessage(guess.first.getAccountID(), guess.second, txt);
} else {
mCallbacks.getService().sendTextMessage(conference, txt);
mConversationFacade.sendTextMessage(conference, txt);
}
}
......
......@@ -74,6 +74,7 @@ import cx.ring.application.RingApplication;
import cx.ring.client.ConversationActivity;
import cx.ring.client.HomeActivity;
import cx.ring.client.QRCodeScannerActivity;
import cx.ring.facades.ConversationFacade;
import cx.ring.model.Account;
import cx.ring.model.CallContact;
import cx.ring.model.Conference;
......@@ -83,7 +84,6 @@ import cx.ring.model.ServiceEvent;
import cx.ring.model.Uri;
import cx.ring.service.LocalService;
import cx.ring.services.AccountService;
import cx.ring.services.CallService;
import cx.ring.services.ContactService;
import cx.ring.utils.ActionHelper;
import cx.ring.utils.BlockchainInputHandler;
......@@ -148,6 +148,9 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
@Inject
ContactService mContactService;
@Inject
ConversationFacade mConversationFacade;
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
......@@ -174,8 +177,7 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
return;
}
LocalService service = mCallbacks.getService();
service.updateConversationContactWithRingId(name, address);
mConversationFacade.updateConversationContactWithRingId(name, address);
RingApplication.uiHandler.post(new Runnable() {
@Override
public void run() {
......@@ -224,7 +226,7 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
if (mSmartListAdapter == null) {
bindService(getActivity(), service);
} else {
mSmartListAdapter.updateDataset(service.getConversations(), null);
mSmartListAdapter.updateDataset(mConversationFacade.getConversationsList(), null);
}
if (service.isConnected()) {
......@@ -245,7 +247,7 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
if (service == null || !service.isConnected() || !service.areConversationsLoaded()) {
return;
}
List<Conversation> conversations = service.getConversations();
List<Conversation> conversations = mConversationFacade.getConversationsList();
for (Conversation conversation : conversations) {
CallContact contact = conversation.getContact();
if (contact == null) {
......@@ -309,6 +311,7 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
mAccountService.addObserver(this);
mAccountService.addObserver(mRinguifyObserver);
mConversationFacade.addObserver(this);
Log.d(TAG, "onResume");
((HomeActivity) getActivity()).setToolbarState(false, R.string.app_name);
if (mSmartListAdapter != null) {
......@@ -455,7 +458,7 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
Log.d(TAG, "onQueryTextChange: null service");
} else {
mSmartListAdapter.updateDataset(
mCallbacks.getService().getConversations(),
mConversationFacade.getConversationsList(),
query
);
}
......@@ -508,6 +511,7 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
getActivity().unregisterReceiver(receiver);
mAccountService.removeObserver(this);
mAccountService.removeObserver(mRinguifyObserver);
mConversationFacade.removeObserver(this);
}
@OnClick(R.id.newcontact_element)
......@@ -538,7 +542,7 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
service.get40dpContactCache(),
service.getThreadPool());
mSmartListAdapter.updateDataset(service.getConversations(), null);
mSmartListAdapter.updateDataset(mConversationFacade.getConversationsList(), null);
mSmartListAdapter.setCallback(this);
if (mList != null) {
mList.setAdapter(mSmartListAdapter);
......@@ -863,54 +867,59 @@ public class SmartListFragment extends Fragment implements SearchView.OnQueryTex
mNewContact.setVisibility(View.VISIBLE);
}
private void handleRegisterNameFound(final String name, final String address, final int state) {
RingApplication.uiHandler.post(new Runnable() {
@Override
public void run() {
switch (state) {
case 0:
// on found
if (!TextUtils.isEmpty(mLastBlockchainQuery) && mLastBlockchainQuery.equals(name)) {
displayNewContactRowWithName(name, address);
mLastBlockchainQuery = null;
} else {
mNewContact.setVisibility(View.GONE);
}
break;
case 1:
// invalid name
Uri uriName = new Uri(name);
if (uriName.isRingId()) {
displayNewContactRowWithName(name, null);
} else {
mNewContact.setVisibility(View.GONE);
}
break;
default:
// on error
Uri uriAddress = new Uri(address);
if (uriAddress.isRingId()) {
displayNewContactRowWithName(name, address);
} else {
mNewContact.setVisibility(View.GONE);
}
break;
}
}
});
}
@Override
public void update(Observable observable, final ServiceEvent event) {
if (event == null) {
return;
}
if (event.getEventType() == ServiceEvent.EventType.REGISTERED_NAME_FOUND) {
if (!isSearching) {
return;
}
RingApplication.uiHandler.post(new Runnable() {
@Override
public void run() {
String name = event.getEventInput(ServiceEvent.EventInput.NAME, String.class);
String address = event.getEventInput(ServiceEvent.EventInput.ADDRESS, String.class);
int state = event.getEventInput(ServiceEvent.EventInput.STATE, Integer.class);
switch (state) {
case 0:
// on found
if (!TextUtils.isEmpty(mLastBlockchainQuery) && mLastBlockchainQuery.equals(name)) {
displayNewContactRowWithName(name, address);
mLastBlockchainQuery = null;
} else {
mNewContact.setVisibility(View.GONE);
}
break;
case 1:
// invalid name
Uri uriName = new Uri(name);
if (uriName.isRingId()) {
displayNewContactRowWithName(name, null);
} else {
mNewContact.setVisibility(View.GONE);
}
break;
default:
// on error
Uri uriAddress = new Uri(address);
if (uriAddress.isRingId()) {
displayNewContactRowWithName(name, address);
} else {
mNewContact.setVisibility(View.GONE);
}
break;
}
switch (event.getEventType()) {
case REGISTERED_NAME_FOUND:
String name = event.getEventInput(ServiceEvent.EventInput.NAME, String.class);
if (TextUtils.isEmpty(mLastBlockchainQuery) || !mLastBlockchainQuery.equals(name)) {
return;
}
});
String address = event.getEventInput(ServiceEvent.EventInput.ADDRESS, String.class);
int state = event.getEventInput(ServiceEvent.EventInput.STATE, Integer.class);
handleRegisterNameFound(name, address, state);
break;
}
}
}
/*
* Copyright (C) 2016 Savoir-faire Linux Inc.
*
* Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package cx.ring.facades;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import javax.inject.Inject;
import cx.ring.model.Account;
import cx.ring.model.CallContact;
import cx.ring.model.Conference;
import cx.ring.model.ConfigKey;
import cx.ring.model.Conversation;
import cx.ring.model.HistoryCall;
import cx.ring.model.HistoryEntry;
import cx.ring.model.HistoryText;
import cx.ring.model.SecureSipCall;
import cx.ring.model.ServiceEvent;
import cx.ring.model.SipCall;
import cx.ring.model.TextMessage;
import cx.ring.model.Uri;
import cx.ring.services.AccountService;
import cx.ring.services.CallService;
import cx.ring.services.ConferenceService;
import cx.ring.services.ContactService;
import cx.ring.services.DeviceRuntimeService;
import cx.ring.services.HardwareService;
import cx.ring.services.HistoryService;
import cx.ring.services.NotificationService;
import cx.ring.utils.Log;
import cx.ring.utils.Observable;
import cx.ring.utils.Observer;
import cx.ring.utils.Tuple;
/**
* This facade handles the conversations
* - Load from the history
* - Keep a local cache of these conversations
* <p>
* Events are broadcasted:
* - CONVERSATIONS_CHANGED
*/
public class ConversationFacade extends Observable implements Observer<ServiceEvent> {
private final static String TAG = ConversationFacade.class.getName();
@Inject
AccountService mAccountService;
@Inject
ContactService mContactService;
@Inject
ConferenceService mConferenceService;
private HistoryService mHistoryService;
@Inject
CallService mCallService;
@Inject
HardwareService mHardwareService;
@Inject
NotificationService mNotificationService;
@Inject
DeviceRuntimeService mDeviceRuntimeService;
private Map<String, Conversation> mConversationMap;
public ConversationFacade(HistoryService historyService) {
mConversationMap = new HashMap<>();
mHistoryService = historyService;
mHistoryService.addObserver(this);
}
/**
* Loads conversations from history calls and texts (also sends CONVERSATIONS_CHANGED event)
*/
public void loadConversationsFromHistory() {
try {
mHistoryService.getCallAndTextAsync();
} catch (SQLException e) {