diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java index 2a088edd0399a58bac0222f5c6892c3efd7b1565..e4d5b7578e1eee2c6d15a5617282a1996f4d8895 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java @@ -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 diff --git a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java index 3450ae0b85dcef69c720af9b90b7fa5f38e60c4c..e73417ba63e39a2646e40c312dfa65bfbc369a41 100644 --- a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java @@ -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; diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java index e45e83582ebd6b697ff29b354e7c6c4fa7583381..56977f7e51d13de4cd3d92f845428255be2ab10e 100644 --- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java @@ -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; } diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java index 5d76ce51d0b557a2190f4e0fd67bc0fa1f31c16c..2c1737123c98de382c99a98cec5e92ccb103d7fd 100755 --- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java +++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java @@ -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); diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java index 8661d07f79596532269e9dbddc5d4b5f3fad2dd1..a590ff075d04cd7aecc5a1b510a3977c685b487a 100755 --- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java +++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java @@ -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 diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java index 0257397a249fa10e2bc689bcf1067f7fe67a048a..9bb3bdfb9b1e26aaa7a85276373406ab6670e14a 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java @@ -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(); diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java index f8850916033dd37deb928a04eb93573ab508635c..d5c9b50575262526ba194342628c0f223f98ef3f 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java @@ -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); } } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java index d3b06a93bf0c7ce86d9adb7b22ef08e4871f16b6..0bed049f0feb144dc3ede62610bac0880c77bc60 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java @@ -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; } } } diff --git a/ring-android/app/src/main/java/cx/ring/service/LocalService.java b/ring-android/app/src/main/java/cx/ring/service/LocalService.java index bb523ce0a54b36ac16abebcd604a7e7e85c258be..0ed8665497dc329ab8dbd068edcbd87ea0cc2671 100644 --- a/ring-android/app/src/main/java/cx/ring/service/LocalService.java +++ b/ring-android/app/src/main/java/cx/ring/service/LocalService.java @@ -22,7 +22,6 @@ package cx.ring.service; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -33,26 +32,17 @@ import android.graphics.Bitmap; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.AsyncTask; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.preference.PreferenceManager; import android.provider.ContactsContract; -import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; -import android.util.Pair; -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 java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -60,14 +50,13 @@ import javax.inject.Inject; import cx.ring.BuildConfig; import cx.ring.application.RingApplication; +import cx.ring.facades.ConversationFacade; 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.Settings; @@ -86,8 +75,6 @@ import cx.ring.utils.ContentUriHandler; import cx.ring.utils.MediaManager; import cx.ring.utils.Observable; import cx.ring.utils.Observer; -import cx.ring.utils.Tuple; - public class LocalService extends Service implements Observer<ServiceEvent> { static final String TAG = LocalService.class.getSimpleName(); @@ -127,6 +114,9 @@ public class LocalService extends Service implements Observer<ServiceEvent> { @Inject HardwareService mHardwareService; + @Inject + ConversationFacade mConversationFacade; + private IDRingService mService = null; private boolean dringStarted = false; @@ -135,9 +125,6 @@ public class LocalService extends Service implements Observer<ServiceEvent> { // Binder given to clients private final IBinder mBinder = new LocalBinder(); - private Map<String, Conversation> conversations = new HashMap<>(); - private LongSparseArray<TextMessage> messages = new LongSparseArray<>(); - private LruCache<Long, Bitmap> mMemoryCache = null; private final ExecutorService mPool = Executors.newCachedThreadPool(); @@ -146,7 +133,6 @@ public class LocalService extends Service implements Observer<ServiceEvent> { private boolean isWifiConn = false; private boolean isMobileConn = false; - private boolean canUseContacts = true; private boolean canUseMobile = false; private boolean mAreConversationsLoaded = false; @@ -163,132 +149,10 @@ public class LocalService extends Service implements Observer<ServiceEvent> { return isWifiConn || (canUseMobile && isMobileConn); } - public boolean isWifiConnected() { - return isWifiConn; - } - public boolean isMobileNetworkConnectedButNotGranted() { return (!canUseMobile && isMobileConn); } - public Conference placeCall(SipCall call) { - Conference conf = null; - CallContact contact = call.getContact(); - if (contact == null) { - contact = mContactService.findContactByNumber(call.getNumberUri().getRawUriString()); - } - Conversation conv = startConversation(contact); - try { - mHardwareService.setPreviewSettings(mDeviceRuntimeService.retrieveAvailablePreviewSettings()); - Uri number = call.getNumberUri(); - if (number == null || number.isEmpty()) { - number = contact.getPhones().get(0).getNumber(); - } - String callId = mService.placeCall(call.getAccount(), number.getUriString(), !call.isVideoMuted()); - if (callId == null || callId.isEmpty()) { - return null; - } - call.setCallID(callId); - Account account = mAccountService.getAccount(call.getAccount()); - if (account.isRing() - || account.getDetailBoolean(ConfigKey.SRTP_ENABLE) - || account.getDetailBoolean(ConfigKey.TLS_ENABLE)) { - Log.i(TAG, "placeCall() call is secure"); - SecureSipCall secureCall = new SecureSipCall(call, account.getDetail(ConfigKey.SRTP_KEY_EXCHANGE)); - conf = new Conference(secureCall); - } else { - conf = new Conference(call); - } - conf.getParticipants().get(0).setContact(contact); - conv.addConference(conf); - } catch (RemoteException e) { - Log.e(TAG, "placeCall", e); - } - return conf; - } - - public void sendTextMessage(String account, Uri to, String txt) { - try { - long id = mService.sendAccountTextMessage(account, to.getRawUriString(), txt); - Log.i(TAG, "sendAccountTextMessage " + txt + " got id " + id); - TextMessage message = new TextMessage(false, txt, to, null, account); - message.setID(id); - message.read(); - mHistoryService.insertNewTextMessage(message); - messages.put(id, message); - textMessageSent(message); - } catch (RemoteException e) { - Log.e(TAG, "sendTextMessage", e); - } - } - - public void sendTextMessage(Conference conf, String txt) { - try { - mService.sendTextMessage(conf.getId(), txt); - SipCall call = conf.getParticipants().get(0); - TextMessage message = new TextMessage(false, txt, call.getNumberUri(), conf.getId(), call.getAccount()); - message.read(); - mHistoryService.insertNewTextMessage(message); - textMessageSent(message); - } catch (RemoteException e) { - Log.e(TAG, "sendTextMessage", e); - } - } - - private void readTextMessage(TextMessage message) { - message.read(); - HistoryText ht = new HistoryText(message); - mHistoryService.updateTextMessage(ht); - } - - public void readConversation(Conversation conv) { - for (HistoryEntry h : conv.getRawHistory().values()) { - NavigableMap<Long, TextMessage> messages = h.getTextMessages(); - for (TextMessage msg : messages.descendingMap().values()) { - if (msg.isRead()) { - break; - } - readTextMessage(msg); - } - } - mNotificationService.cancelTextNotification(conv.getContact()); - updateTextNotifications(); - } - - private Conversation conversationFromMessage(TextMessage txt) { - Conversation conv; - String call = txt.getCallId(); - if (call != null && !call.isEmpty()) { - conv = getConversationByCallId(call); - } else { - conv = startConversation(mContactService.findContactByNumber(txt.getNumberUri().getRawUriString())); - txt.setContact(conv.getContact()); - } - return conv; - } - - private void textMessageSent(TextMessage txt) { - Log.d(TAG, "Sent text messsage " + txt.getAccount() + " " + txt.getCallId() + " " + txt.getNumberUri() + " " + txt.getMessage()); - Conversation conv = conversationFromMessage(txt); - conv.addTextMessage(txt); - if (conv.isVisible()) { - txt.read(); - } else { - updateTextNotifications(); - } - sendBroadcast(new Intent(ACTION_CONF_UPDATE)); - } - - public void refreshConversations() { - Log.d(TAG, "refreshConversations()"); - new ConversationLoader(getApplicationContext().getContentResolver()) { - @Override - protected void onPostExecute(Map<String, Conversation> res) { - updated(res); - } - }.execute(); - } - public void reloadAccounts() { if (mService != null) { //initAccountLoader(); @@ -345,12 +209,12 @@ public class LocalService extends Service implements Observer<ServiceEvent> { mSettingsService.addObserver(this); mAccountService.addObserver(this); mContactService.addObserver(this); + mConversationFacade.addObserver(this); // Clear any notifications from a previous app instance mNotificationService.cancelAll(); Settings settings = mSettingsService.loadSettings(); - canUseContacts = settings.isAllowSystemContacts(); canUseMobile = settings.isAllowMobileData(); startDRingService(); @@ -384,6 +248,7 @@ public class LocalService extends Service implements Observer<ServiceEvent> { mSettingsService.removeObserver(this); mAccountService.removeObserver(this); mContactService.removeObserver(this); + mConversationFacade.removeObserver(this); stopListener(); mMemoryCache.evictAll(); mPool.shutdown(); @@ -394,7 +259,7 @@ public class LocalService extends Service implements Observer<ServiceEvent> { public void onServiceConnected(ComponentName className, IBinder service) { Log.w(TAG, "onServiceConnected " + className.getClassName()); mService = IDRingService.Stub.asInterface(service); - refreshConversations(); + mConversationFacade.refreshConversations(); } @Override @@ -443,275 +308,8 @@ public class LocalService extends Service implements Observer<ServiceEvent> { return mService; } - public ArrayList<Conversation> getConversations() { - ArrayList<Conversation> convs = new ArrayList<>(conversations.values()); - Collections.sort(convs, new Comparator<Conversation>() { - @Override - public int compare(Conversation lhs, Conversation rhs) { - return (int) ((rhs.getLastInteraction().getTime() - lhs.getLastInteraction().getTime()) / 1000l); - } - }); - return convs; - } - - public Conversation getConversation(String id) { - return conversations.get(id); - } - - public Conference getConference(String id) { - for (Conversation conv : conversations.values()) { - Conference conf = conv.getConference(id); - if (conf != null) { - return conf; - } - } - return null; - } - - public Pair<Conference, SipCall> getCall(String id) { - for (Conversation conv : conversations.values()) { - ArrayList<Conference> confs = conv.getCurrentCalls(); - for (Conference c : confs) { - SipCall call = c.getCallById(id); - if (call != null) { - return new Pair<>(c, call); - } - } - } - return new Pair<>(null, null); - } - - public Conversation getByContact(CallContact contact) { - ArrayList<String> keys = contact.getIds(); - for (String key : keys) { - Conversation conversation = conversations.get(key); - if (conversation != null) { - return conversation; - } - } - Log.w(TAG, "getByContact failed"); - return null; - } - - public Conversation getConversationByCallId(String callId) { - for (Conversation conversation : conversations.values()) { - Conference conf = conversation.getConference(callId); - if (conf != null) { - return conversation; - } - } - return null; - } - - public Conversation startConversation(CallContact contact) { - if (contact.isUnknown()) { - contact = mContactService.findContactByNumber(contact.getPhones().get(0).getNumber().getRawUriString()); - } - Conversation conversation = getByContact(contact); - if (conversation == null) { - conversation = new Conversation(contact); - conversations.put(contact.getIds().get(0), conversation); - } - return conversation; - } - - public void updateConversationContactWithRingId(String oldId, String ringId) { - - if (TextUtils.isEmpty(oldId)) { - return; - } - - Uri uri = new Uri(oldId); - if (uri.isRingId()) { - return; - } - - Conversation conversation = conversations.get(oldId); - if (conversation == null) { - return; - } - - CallContact contact = conversation.getContact(); - - if (contact == null) { - return; - } - - Uri ringIdUri = new Uri(ringId); - contact.getPhones().clear(); - contact.getPhones().add(new cx.ring.model.Phone(ringIdUri, 0)); - contact.resetDisplayName(); - - conversations.remove(oldId); - conversations.put(contact.getIds().get(0), conversation); - - for (Map.Entry<String, HistoryEntry> entry : conversation.getHistory().entrySet()) { - HistoryEntry historyEntry = entry.getValue(); - historyEntry.setContact(contact); - NavigableMap<Long, TextMessage> messages = historyEntry.getTextMessages(); - for (TextMessage textMessage : messages.values()) { - textMessage.setNumber(ringIdUri); - textMessage.setContact(contact); - mHistoryService.updateTextMessage(new HistoryText(textMessage)); - } - } - - return; - } - - public Conversation findConversationByNumber(Uri number) { - if (number == null || number.isEmpty()) { - return null; - } - for (Conversation conversation : conversations.values()) { - if (conversation.getContact().hasNumber(number)) { - return conversation; - } - } - return startConversation(mContactService.findContactByNumber(number.getRawUriString())); - } - - public void clearHistory() { - mHistoryService.clearHistory(); - refreshConversations(); - } - - private class ConversationLoader extends AsyncTask<Void, Void, Map<String, Conversation>> { - private final ContentResolver cr; - - public ConversationLoader(ContentResolver c) { - cr = c; - } - - Tuple<HistoryEntry, HistoryCall> findHistoryByCallId(final Map<String, Conversation> confs, String id) { - for (Conversation c : confs.values()) { - Tuple<HistoryEntry, HistoryCall> h = c.findHistoryByCallId(id); - if (h != null) { - return h; - } - } - return null; - } - - @Override - protected Map<String, Conversation> doInBackground(Void... params) { - final Map<String, Conversation> ret = new HashMap<>(); - - if (mService == null) { - return ret; - } - - try { - final List<HistoryCall> history = mHistoryService.getAll(); - final List<HistoryText> historyTexts = mHistoryService.getAllTextMessages(); - - final Map<String, ArrayList<String>> confs = mService.getConferenceList(); - - for (HistoryCall call : history) { - CallContact contact = mContactService.findContact(call.getContactID(), call.getContactKey(), call.getNumber()); - - String key = contact.getIds().get(0); - if (conversations.containsKey(key)) { - if (!conversations.get(key).getHistoryCalls().contains(call)) { - conversations.get(key).addHistoryCall(call); - } - } else { - Conversation conversation = new Conversation(contact); - conversation.addHistoryCall(call); - conversations.put(key, conversation); - } - } - - for (HistoryText htext : historyTexts) { - CallContact contact = mContactService.findContact(htext.getContactID(), htext.getContactKey(), htext.getNumber()); - Tuple<HistoryEntry, HistoryCall> p = findHistoryByCallId(ret, htext.getCallId()); - - if (contact == null && p != null) { - contact = p.first.getContact(); - } - if (contact == null) { - continue; - } - - TextMessage msg = new TextMessage(htext); - msg.setContact(contact); - - if (p != null) { - if (msg.getNumberUri() == null) { - msg.setNumber(new Uri(p.second.getNumber())); - } - p.first.addTextMessage(msg); - } - - String key = contact.getIds().get(0); - if (conversations.containsKey(key)) { - if (!conversations.get(key).getTextMessages().contains(msg)) { - conversations.get(key).addTextMessage(msg); - } - } else { - Conversation c = new Conversation(contact); - c.addTextMessage(msg); - conversations.put(key, c); - } - } - - for (Map.Entry<String, ArrayList<String>> c : confs.entrySet()) { - Conference conf = new Conference(c.getKey()); - for (String call_id : c.getValue()) { - SipCall call = getCall(call_id).second; - if (call == null) { - call = new SipCall(call_id, mService.getCallDetails(call_id)); - } - Account acc = mAccountService.getAccount(call.getAccount()); - if (acc.isRing() - || acc.getDetailBoolean(ConfigKey.SRTP_ENABLE) - || acc.getDetailBoolean(ConfigKey.TLS_ENABLE)) { - call = new SecureSipCall(call, acc.getDetail(ConfigKey.SRTP_KEY_EXCHANGE)); - } - conf.addParticipant(call); - } - List<SipCall> calls = conf.getParticipants(); - if (calls.size() == 1) { - SipCall call = calls.get(0); - CallContact contact = mContactService.findContact(-1, null, call.getNumber()); - call.setContact(contact); - - Conversation conv = null; - ArrayList<String> ids = contact.getIds(); - for (String id : ids) { - conv = conversations.get(id); - if (conv != null) { - break; - } - } - if (conv != null) { - conv.addConference(conf); - } else { - conv = new Conversation(contact); - conv.addConference(conf); - conversations.put(ids.get(0), conv); - } - } - } - for (Conversation c : conversations.values()) { - Log.w(TAG, "Conversation : " + c.getContact().getId() + " " + c.getContact().getDisplayName() + " " + c.getLastNumberUsed(c.getLastAccountUsed()) + " " + c.getLastInteraction().toString()); - } - - for (CallContact contact : mContactService.getContacts()) { - String key = contact.getIds().get(0); - if (!conversations.containsKey(key)) { - conversations.put(key, new Conversation(contact)); - } - } - } catch (Exception e) { - Log.e(TAG, "ConversationLoader doInBackground", e); - } - return conversations; - } - } - private void updated(Map<String, Conversation> res) { - for (Conversation conversation : conversations.values()) { + for (Conversation conversation : mConversationFacade.getConversations().values()) { boolean isConversationVisible = conversation.isVisible(); String conversationKey = conversation.getContact().getIds().get(0); Conversation newConversation = res.get(conversationKey); @@ -719,9 +317,8 @@ public class LocalService extends Service implements Observer<ServiceEvent> { newConversation.setVisible(isConversationVisible); } } - conversations = res; updateAudioState(); - updateTextNotifications(); + mConversationFacade.updateTextNotifications(); sendBroadcast(new Intent(ACTION_CONF_UPDATE)); sendBroadcast(new Intent(ACTION_CONF_LOADED)); mAreConversationsLoaded = true; @@ -748,32 +345,10 @@ public class LocalService extends Service implements Observer<ServiceEvent> { } } - public void updateTextNotifications() { - Log.d(TAG, "updateTextNotifications()"); - - for (Conversation conversation : conversations.values()) { - - if (conversation.isVisible()) { - mNotificationService.cancelTextNotification(conversation.getContact()); - continue; - } - - TreeMap<Long, TextMessage> texts = conversation.getUnreadTextMessages(); - if (texts.isEmpty() || texts.lastEntry().getValue().isNotified()) { - continue; - } else { - mNotificationService.cancelTextNotification(conversation.getContact()); - } - - CallContact contact = conversation.getContact(); - mNotificationService.showTextNotification(contact, conversation, texts); - } - } - private void updateAudioState() { boolean current = false; Conference ringing = null; - for (Conversation c : conversations.values()) { + for (Conversation c : mConversationFacade.getConversations().values()) { Conference conf = c.getCurrentCall(); if (conf != null) { current = true; @@ -816,9 +391,9 @@ public class LocalService extends Service implements Observer<ServiceEvent> { } case ACTION_CONV_READ: { String convId = intent.getData().getLastPathSegment(); - Conversation conversation = getConversation(convId); + Conversation conversation = mConversationFacade.getConversationById(convId); if (conversation != null) { - readConversation(conversation); + mConversationFacade.readConversation(conversation); } sendBroadcast(new Intent(ACTION_CONF_UPDATE).setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONVERSATION_CONTENT_URI, convId))); @@ -832,7 +407,7 @@ public class LocalService extends Service implements Observer<ServiceEvent> { Log.e(TAG, "ACTION_CALL_ACCEPT", e); } updateAudioState(); - Conference conf = getConference(callId); + Conference conf = mConversationFacade.getConference(callId); if (conf != null && !conf.isVisible()) { startActivity(ActionHelper.getViewIntent(LocalService.this, conf).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @@ -874,9 +449,9 @@ public class LocalService extends Service implements Observer<ServiceEvent> { Conversation conversation; if (call != null && !call.isEmpty()) { - conversation = getConversationByCallId(call); + conversation = mConversationFacade.getConversationByCallId(call); } else { - conversation = startConversation(mContactService.findContactByNumber(txt.getNumberUri().getRawUriString())); + conversation = mConversationFacade.startConversation(mContactService.findContactByNumber(txt.getNumberUri().getRawUriString())); txt.setContact(conversation.getContact()); } if (conversation.isVisible()) { @@ -890,28 +465,12 @@ public class LocalService extends Service implements Observer<ServiceEvent> { sendBroadcast(new Intent(ACTION_CONF_UPDATE)); break; } - case ConfigurationManagerCallback.MESSAGE_STATE_CHANGED: { - long id = intent.getLongExtra(ConfigurationManagerCallback.MESSAGE_STATE_CHANGED_EXTRA_ID, 0); - int status = intent.getIntExtra( - ConfigurationManagerCallback.MESSAGE_STATE_CHANGED_EXTRA_STATUS, - TextMessage.Status.UNKNOWN.toInt() - ); - TextMessage msg = messages.get(id); - if (msg != null) { - Log.d(TAG, "Message status changed " + id + " " + status); - msg.setStatus(status); - sendBroadcast(new Intent(ACTION_CONF_UPDATE). - putExtra(ACTION_CONF_UPDATE_EXTRA_MSG, id) - ); - } - break; - } case CallManagerCallBack.INCOMING_CALL: { String callId = intent.getStringExtra("call"); String accountId = intent.getStringExtra("account"); Uri number = new Uri(intent.getStringExtra("from")); CallContact contact = mContactService.findContactByNumber(number.getRawUriString()); - Conversation conversation = startConversation(contact); + Conversation conversation = mConversationFacade.startConversation(contact); SipCall call = new SipCall(callId, accountId, number, SipCall.Direction.INCOMING); call.setContact(contact); @@ -953,7 +512,7 @@ public class LocalService extends Service implements Observer<ServiceEvent> { Conversation conversation = null; Conference found = null; - for (Conversation conv : conversations.values()) { + for (Conversation conv : mConversationFacade.getConversations().values()) { Conference tconf = conv.getConference(callId); if (tconf != null) { conversation = conv; @@ -1028,7 +587,7 @@ public class LocalService extends Service implements Observer<ServiceEvent> { // no refresh here break; default: - refreshConversations(); + mConversationFacade.refreshConversations(); } } }; @@ -1093,18 +652,27 @@ public class LocalService extends Service implements Observer<ServiceEvent> { public void deleteConversation(Conversation conversation) { mHistoryService.clearHistoryForConversation(conversation); - refreshConversations(); } @Override public void update(Observable observable, ServiceEvent arg) { if (observable instanceof HistoryService) { - refreshConversations(); + + if(arg != null) { + switch (arg.getEventType()) { + case HISTORY_LOADED: + break; + default: + mConversationFacade.refreshConversations(); + break; + } + } else { + mConversationFacade.refreshConversations(); + } } if (observable instanceof SettingsService) { - canUseContacts = mSettingsService.loadSettings().isAllowSystemContacts(); canUseMobile = mSettingsService.loadSettings().isAllowMobileData(); refreshContacts(); updateConnectivityState(); @@ -1127,7 +695,16 @@ public class LocalService extends Service implements Observer<ServiceEvent> { if (observable instanceof ContactService && arg != null) { switch (arg.getEventType()) { case CONTACTS_CHANGED: - refreshConversations(); + mConversationFacade.refreshConversations(); + break; + } + } + + if (observable instanceof ConversationFacade && arg != null) { + switch (arg.getEventType()) { + case CONVERSATIONS_CHANGED: + Map<String, Conversation> conversations = mConversationFacade.getConversations(); + updated(conversations); break; } } diff --git a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java new file mode 100644 index 0000000000000000000000000000000000000000..4e31737e8c3fc369857ceeaf4efd21e2c609aa9d --- /dev/null +++ b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java @@ -0,0 +1,496 @@ +/* + * 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) { + Log.e(TAG, "unable to retrieve history calls and texts", e); + return; + } + + } + + private Tuple<HistoryEntry, HistoryCall> findHistoryByCallId(final Map<String, Conversation> conversations, String id) { + for (Conversation conversation : conversations.values()) { + Tuple<HistoryEntry, HistoryCall> historyCall = conversation.findHistoryByCallId(id); + if (historyCall != null) { + return historyCall; + } + } + return null; + } + + private Tuple<Conference, SipCall> getCall(String id) { + for (Conversation conv : mConversationMap.values()) { + ArrayList<Conference> confs = conv.getCurrentCalls(); + for (Conference c : confs) { + SipCall call = c.getCallById(id); + if (call != null) { + return new Tuple<>(c, call); + } + } + } + return new Tuple<>(null, null); + } + + /** + * @return the local cache of conversations + */ + public Map<String, Conversation> getConversations() { + return mConversationMap; + } + + /** + * @param contact + * @return + */ + public Conversation getConversationByContact(CallContact contact) { + ArrayList<String> keys = contact.getIds(); + for (String key : keys) { + Conversation conversation = mConversationMap.get(key); + if (conversation != null) { + return conversation; + } + } + return null; + } + + /** + * @param callId + * @return + */ + public Conversation getConversationByCallId(String callId) { + for (Conversation conversation : mConversationMap.values()) { + Conference conf = conversation.getConference(callId); + if (conf != null) { + return conversation; + } + } + return null; + } + + /** + * @param contact + * @return the started new conversation + */ + public Conversation startConversation(CallContact contact) { + if (contact.isUnknown()) { + contact = mContactService.findContactByNumber(contact.getPhones().get(0).getNumber().getRawUriString()); + } + Conversation conversation = getConversationByContact(contact); + if (conversation == null) { + conversation = new Conversation(contact); + mConversationMap.put(contact.getIds().get(0), conversation); + } + + setChanged(); + ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.CONVERSATIONS_CHANGED); + notifyObservers(event); + + return conversation; + } + + /** + * @return the conversation local cache in a List + */ + public ArrayList<Conversation> getConversationsList() { + ArrayList<Conversation> convs = new ArrayList<>(mConversationMap.values()); + Collections.sort(convs, new Comparator<Conversation>() { + @Override + public int compare(Conversation lhs, Conversation rhs) { + return (int) ((rhs.getLastInteraction().getTime() - lhs.getLastInteraction().getTime()) / 1000l); + } + }); + return convs; + } + + /** + * @param id + * @return the conversation from the local cache + */ + public Conversation getConversationById(String id) { + return mConversationMap.get(id); + } + + /** + * (also sends CONVERSATIONS_CHANGED event) + * + * @param oldId + * @param ringId + */ + public void updateConversationContactWithRingId(String oldId, String ringId) { + + if (oldId == null || oldId.isEmpty()) { + return; + } + + Uri uri = new Uri(oldId); + if (uri.isRingId()) { + return; + } + + Conversation conversation = mConversationMap.get(oldId); + if (conversation == null) { + return; + } + + CallContact contact = conversation.getContact(); + + if (contact == null) { + return; + } + + Uri ringIdUri = new Uri(ringId); + contact.getPhones().clear(); + contact.getPhones().add(new cx.ring.model.Phone(ringIdUri, 0)); + contact.resetDisplayName(); + + mConversationMap.remove(oldId); + mConversationMap.put(contact.getIds().get(0), conversation); + + for (Map.Entry<String, HistoryEntry> entry : conversation.getHistory().entrySet()) { + HistoryEntry historyEntry = entry.getValue(); + historyEntry.setContact(contact); + NavigableMap<Long, TextMessage> messages = historyEntry.getTextMessages(); + for (TextMessage textMessage : messages.values()) { + textMessage.setNumber(ringIdUri); + textMessage.setContact(contact); + mHistoryService.updateTextMessage(new HistoryText(textMessage)); + } + } + + setChanged(); + ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.CONVERSATIONS_CHANGED); + notifyObservers(event); + } + + public Conversation findOrStartConversationByNumber(Uri number) { + if (number == null || number.isEmpty()) { + return null; + } + + for (Conversation conversation : mConversationMap.values()) { + if (conversation.getContact().hasNumber(number)) { + return conversation; + } + } + + return startConversation(mContactService.findContactByNumber(number.getRawUriString())); + } + + public Conversation getConversationFromMessage(TextMessage txt) { + Conversation conv; + String call = txt.getCallId(); + if (call != null && !call.isEmpty()) { + conv = getConversationByCallId(call); + } else { + conv = startConversation(mContactService.findContactByNumber(txt.getNumberUri().getRawUriString())); + txt.setContact(conv.getContact()); + } + return conv; + } + + public Conference placeCall(SipCall call) { + Conference conf; + CallContact contact = call.getContact(); + if (contact == null) { + contact = mContactService.findContactByNumber(call.getNumberUri().getRawUriString()); + } + Conversation conv = startConversation(contact); + mHardwareService.setPreviewSettings(mDeviceRuntimeService.retrieveAvailablePreviewSettings()); + Uri number = call.getNumberUri(); + if (number == null || number.isEmpty()) { + number = contact.getPhones().get(0).getNumber(); + } + String callId = mCallService.placeCall(call.getAccount(), number.getUriString(), !call.isVideoMuted()); + if (callId == null || callId.isEmpty()) { + return null; + } + call.setCallID(callId); + Account account = mAccountService.getAccount(call.getAccount()); + if (account.isRing() + || account.getDetailBoolean(ConfigKey.SRTP_ENABLE) + || account.getDetailBoolean(ConfigKey.TLS_ENABLE)) { + Log.i(TAG, "placeCall() call is secure"); + SecureSipCall secureCall = new SecureSipCall(call, account.getDetail(ConfigKey.SRTP_KEY_EXCHANGE)); + conf = new Conference(secureCall); + } else { + conf = new Conference(call); + } + conf.getParticipants().get(0).setContact(contact); + conv.addConference(conf); + + return conf; + } + + public void sendTextMessage(String account, Uri to, String txt) { + long id = mCallService.sendAccountTextMessage(account, to.getRawUriString(), txt); + Log.i(TAG, "sendAccountTextMessage " + txt + " got id " + id); + TextMessage message = new TextMessage(false, txt, to, null, account); + message.setID(id); + message.read(); + mHistoryService.insertNewTextMessage(message); + } + + public void sendTextMessage(Conference conf, String txt) { + mCallService.sendTextMessage(conf.getId(), txt); + SipCall call = conf.getParticipants().get(0); + TextMessage message = new TextMessage(false, txt, call.getNumberUri(), conf.getId(), call.getAccount()); + message.read(); + mHistoryService.insertNewTextMessage(message); + } + + private void readTextMessage(TextMessage message) { + message.read(); + HistoryText ht = new HistoryText(message); + mHistoryService.updateTextMessage(ht); + } + + public void readConversation(Conversation conv) { + for (HistoryEntry h : conv.getRawHistory().values()) { + NavigableMap<Long, TextMessage> messages = h.getTextMessages(); + for (TextMessage msg : messages.descendingMap().values()) { + if (msg.isRead()) { + break; + } + readTextMessage(msg); + } + } + mNotificationService.cancelTextNotification(conv.getContact()); + updateTextNotifications(); + } + + synchronized public void refreshConversations() { + Log.d(TAG, "refreshConversations()"); + loadConversationsFromHistory(); + } + + public void updateTextNotifications() { + Log.d(TAG, "updateTextNotifications()"); + + for (Conversation conversation : mConversationMap.values()) { + + if (conversation.isVisible()) { + mNotificationService.cancelTextNotification(conversation.getContact()); + continue; + } + + TreeMap<Long, TextMessage> texts = conversation.getUnreadTextMessages(); + if (texts.isEmpty() || texts.lastEntry().getValue().isNotified()) { + continue; + } else { + mNotificationService.cancelTextNotification(conversation.getContact()); + } + + CallContact contact = conversation.getContact(); + mNotificationService.showTextNotification(contact, conversation, texts); + } + } + + public Conference getConference(String id) { + for (Conversation conv : mConversationMap.values()) { + Conference conf = conv.getConference(id); + if (conf != null) { + return conf; + } + } + return null; + } + + @Override + public void update(Observable observable, ServiceEvent event) { + + if (observable instanceof HistoryService && event != null) { + List<HistoryCall> historyCalls = (List<HistoryCall>) event.getEventInput(ServiceEvent.EventInput.HISTORY_CALLS, ArrayList.class); + + for (HistoryCall call : historyCalls) { + CallContact contact = mContactService.findContact(call.getContactID(), call.getContactKey(), call.getNumber()); + + String key = contact.getIds().get(0); + if (mConversationMap.containsKey(key)) { + mConversationMap.get(key).addHistoryCall(call); + } else { + Conversation conversation = new Conversation(contact); + conversation.addHistoryCall(call); + mConversationMap.put(key, conversation); + } + } + + List<HistoryText> historyTexts = (List<HistoryText>) event.getEventInput(ServiceEvent.EventInput.HISTORY_TEXTS, ArrayList.class); + + for (HistoryText htext : historyTexts) { + CallContact contact = mContactService.findContact(htext.getContactID(), htext.getContactKey(), htext.getNumber()); + + TextMessage msg = new TextMessage(htext); + msg.setContact(contact); + + String key = contact.getIds().get(0); + if (mConversationMap.containsKey(key)) { + mConversationMap.get(key).addTextMessage(msg); + } else { + Conversation c = new Conversation(contact); + c.addTextMessage(msg); + mConversationMap.put(key, c); + } + } + + Map<String, ArrayList<String>> conferences = mConferenceService.getConferenceList(); + + for (Map.Entry<String, ArrayList<String>> conferenceEntry : conferences.entrySet()) { + Conference conference = new Conference(conferenceEntry.getKey()); + for (String callId : conferenceEntry.getValue()) { + SipCall call = getCall(callId).second; + if (call == null) { + call = new SipCall(callId, mCallService.getCallDetails(callId)); + } + Account acc = mAccountService.getAccount(call.getAccount()); + if (acc.isRing() + || acc.getDetailBoolean(ConfigKey.SRTP_ENABLE) + || acc.getDetailBoolean(ConfigKey.TLS_ENABLE)) { + call = new SecureSipCall(call, acc.getDetail(ConfigKey.SRTP_KEY_EXCHANGE)); + } + conference.addParticipant(call); + } + List<SipCall> calls = conference.getParticipants(); + if (calls.size() == 1) { + SipCall call = calls.get(0); + CallContact contact = mContactService.findContact(-1, null, call.getNumber()); + call.setContact(contact); + + Conversation conv = null; + ArrayList<String> ids = contact.getIds(); + for (String id : ids) { + conv = mConversationMap.get(id); + if (conv != null) { + break; + } + } + if (conv != null) { + conv.addConference(conference); + } else { + conv = new Conversation(contact); + conv.addConference(conference); + mConversationMap.put(ids.get(0), conv); + } + } + } + for (Conversation conversation : mConversationMap.values()) { + Log.d(TAG, "Conversation : " + conversation.getContact().getId() + " " + conversation.getContact().getDisplayName() + " " + conversation.getLastNumberUsed(conversation.getLastAccountUsed()) + " " + conversation.getLastInteraction().toString()); + } + + for (CallContact contact : mContactService.getContacts()) { + String key = contact.getIds().get(0); + if (!mConversationMap.containsKey(key)) { + mConversationMap.put(key, new Conversation(contact)); + } + } + + setChanged(); + ServiceEvent e = new ServiceEvent(ServiceEvent.EventType.CONVERSATIONS_CHANGED); + notifyObservers(e); + } + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java b/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java index 0d37336b71de5ba2d9f773ee68ed59fc0b1b9086..613e8c3cf8cb95fa96d3d6dd20a6fe8c07187dfb 100644 --- a/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java +++ b/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java @@ -26,7 +26,9 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import java.util.NavigableMap; import java.util.Random; import java.util.Set; import java.util.TreeMap; @@ -163,19 +165,26 @@ public class Conversation { return d; } - public void addHistoryCall(HistoryCall c) { - String accountId = c.getAccountID(); - if (mHistory.containsKey(accountId)) - mHistory.get(accountId).addHistoryCall(c, getContact()); + public void addHistoryCall(HistoryCall call) { + if(getHistoryCalls().contains(call)){ + return; + } + String accountId = call.getAccountID(); + if (mHistory.containsKey(accountId)) { + mHistory.get(accountId).addHistoryCall(call, getContact()); + } else { - HistoryEntry e = new HistoryEntry(accountId, getContact()); - e.addHistoryCall(c, getContact()); - mHistory.put(accountId, e); + HistoryEntry entry = new HistoryEntry(accountId, getContact()); + entry.addHistoryCall(call, getContact()); + mHistory.put(accountId, entry); } - mAggregateHistory.add(new ConversationElement(c)); + mAggregateHistory.add(new ConversationElement(call)); } public void addTextMessage(TextMessage txt) { + if(getTextMessages().contains(txt)){ + return; + } if (txt.getContact() == null) { txt.setContact(getContact()); } @@ -238,16 +247,23 @@ public class Conversation { public Collection<TextMessage> getTextMessages(Date since) { TreeMap<Long, TextMessage> texts = new TreeMap<>(); + for (HistoryEntry h : mHistory.values()) { - texts.putAll(since == null ? h.getTextMessages() : h.getTextMessages(since.getTime())); + Map<Long, TextMessage> textMessages = since == null ? h.getTextMessages() : h.getTextMessages(since.getTime()); + for (Map.Entry<Long, TextMessage> entry : textMessages.entrySet()) { + texts.put(entry.getKey(), entry.getValue()); + } } return texts.values(); } public Collection<HistoryCall> getHistoryCalls() { TreeMap<Long, HistoryCall> calls = new TreeMap<>(); + for (HistoryEntry historyEntry : mHistory.values()) { - calls.putAll(historyEntry.getCalls()); + for (Map.Entry<Long, HistoryCall> entry : historyEntry.getCalls().descendingMap().entrySet()) { + calls.put(entry.getKey(), entry.getValue()); + } } return calls.values(); } diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/ServiceEvent.java b/ring-android/libringclient/src/main/java/cx/ring/model/ServiceEvent.java index 7aa8b72c829db7f70753b0a0e89f15bb3a9e0435..fb099df249f150928cbb64f13496df621fbb91f5 100644 --- a/ring-android/libringclient/src/main/java/cx/ring/model/ServiceEvent.java +++ b/ring-android/libringclient/src/main/java/cx/ring/model/ServiceEvent.java @@ -57,7 +57,9 @@ public class ServiceEvent { MIGRATION_ENDED, INCOMING_TRUST_REQUEST, CONTACT_ADDED, - CONTACT_REMOVED + CONTACT_REMOVED, + CONVERSATIONS_CHANGED, + HISTORY_LOADED } public enum EventInput { @@ -93,7 +95,9 @@ public class ServiceEvent { TIME, MESSAGE, CONFIRMED, - BANNED + BANNED, + HISTORY_CALLS, + HISTORY_TEXTS } private EventType mType; diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/HistoryService.java b/ring-android/libringclient/src/main/java/cx/ring/services/HistoryService.java index a0222154256bc492bbfd845b333fc61ba174ab19..bb335fe1bca2dca631e95e09294fcdc248c1f1e6 100644 --- a/ring-android/libringclient/src/main/java/cx/ring/services/HistoryService.java +++ b/ring-android/libringclient/src/main/java/cx/ring/services/HistoryService.java @@ -29,12 +29,17 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; + +import javax.inject.Inject; +import javax.inject.Named; import cx.ring.model.Conference; import cx.ring.model.Conversation; import cx.ring.model.HistoryCall; import cx.ring.model.HistoryEntry; import cx.ring.model.HistoryText; +import cx.ring.model.ServiceEvent; import cx.ring.model.SipCall; import cx.ring.model.TextMessage; import cx.ring.utils.Log; @@ -51,6 +56,10 @@ public abstract class HistoryService extends Observable { private static final String TAG = HistoryService.class.getSimpleName(); + @Inject + @Named("ApplicationExecutor") + ExecutorService mApplicationExecutor; + protected abstract ConnectionSource getConnectionSource(); protected abstract Dao<HistoryCall, Integer> getCallHistoryDao(); @@ -122,13 +131,34 @@ public abstract class HistoryService extends Observable { return true; } - public List<HistoryCall> getAll() throws SQLException { + public void getCallAndTextAsync() throws SQLException { + + mApplicationExecutor.submit(new Runnable() { + @Override + public void run() { + try { + List<HistoryCall> historyCalls = getAll(); + List<HistoryText> historyTexts = getAllTextMessages(); + + ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.HISTORY_LOADED); + event.addEventInput(ServiceEvent.EventInput.HISTORY_CALLS, historyCalls); + event.addEventInput(ServiceEvent.EventInput.HISTORY_TEXTS, historyTexts); + setChanged(); + notifyObservers(event); + } catch (SQLException e) { + Log.e(TAG, "Can't load calls and texts", e); + } + } + }); + } + + private List<HistoryCall> getAll() throws SQLException { QueryBuilder<HistoryCall, Integer> queryBuilder = getCallHistoryDao().queryBuilder(); queryBuilder.orderBy(HistoryCall.COLUMN_TIMESTAMP_START_NAME, true); return getCallHistoryDao().query(queryBuilder.prepare()); } - public List<HistoryText> getAllTextMessages() throws SQLException { + private List<HistoryText> getAllTextMessages() throws SQLException { QueryBuilder<HistoryText, Integer> queryBuilder = getTextHistoryDao().queryBuilder(); queryBuilder.orderBy(HistoryText.COLUMN_TIMESTAMP_NAME, true); return getTextHistoryDao().query(queryBuilder.prepare());