/* * Copyright (c) 2010-2019 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). * * 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, see . */ package org.linphone.chat; import static android.content.Context.INPUT_METHOD_SERVICE; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.CompoundButton; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.SearchView; import android.widget.Switch; import android.widget.TextView; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.call.views.LinphoneLinearLayoutManager; import org.linphone.contacts.ContactAddress; import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsUpdatedListener; import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.SearchContactViewHolder; import org.linphone.contacts.SearchContactsAdapter; import org.linphone.contacts.views.ContactSelectView; import org.linphone.core.Address; import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoomBackend; import org.linphone.core.ChatRoomListenerStub; import org.linphone.core.ChatRoomParams; import org.linphone.core.Core; import org.linphone.core.Factory; import org.linphone.core.FriendCapability; import org.linphone.core.ProxyConfig; import org.linphone.core.SearchResult; import org.linphone.core.tools.Log; import org.linphone.settings.LinphonePreferences; public class ChatRoomCreationFragment extends Fragment implements View.OnClickListener, SearchContactViewHolder.ClickListener, ContactsUpdatedListener { private RecyclerView mContactsList; private LinearLayout mContactsSelectedLayout; private HorizontalScrollView mContactsSelectLayout; private ImageView mAllContactsButton; private ImageView mLinphoneContactsButton; private ImageView mNextButton; private boolean mOnlyDisplayLinphoneContacts; private View mAllContactsSelected, mLinphoneContactsSelected; private RelativeLayout mSearchLayout, mWaitLayout, mLinphoneContactsToggle, mAllContactsToggle; private SearchView mSearchField; private SearchContactsAdapter mSearchAdapter; private String mChatRoomSubject, mChatRoomAddress; private ChatRoom mChatRoom; private ChatRoomListenerStub mChatRoomCreationListener; private Switch mSecurityToggle; private ArrayList mParticipants; private boolean mCreateGroupChatRoom; private boolean mChatRoomEncrypted; @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); View view = inflater.inflate(R.layout.chat_create, container, false); setRetainInstance(true); mParticipants = new ArrayList<>(); mChatRoomSubject = null; mChatRoomAddress = null; mCreateGroupChatRoom = false; if (getArguments() != null) { if (getArguments().getSerializable("Participants") != null) { mParticipants = (ArrayList) getArguments().getSerializable("Participants"); } mChatRoomSubject = getArguments().getString("Subject"); mChatRoomAddress = getArguments().getString("RemoteSipUri"); mCreateGroupChatRoom = getArguments().getBoolean("IsGroupChatRoom", false); mChatRoomEncrypted = getArguments().getBoolean("Encrypted", false); } mWaitLayout = view.findViewById(R.id.waitScreen); mWaitLayout.setVisibility(View.GONE); mContactsList = view.findViewById(R.id.contactsList); mContactsSelectedLayout = view.findViewById(R.id.contactsSelected); mContactsSelectLayout = view.findViewById(R.id.layoutContactsSelected); mAllContactsButton = view.findViewById(R.id.all_contacts); mAllContactsButton.setOnClickListener(this); mLinphoneContactsButton = view.findViewById(R.id.linphone_contacts); mLinphoneContactsButton.setOnClickListener(this); mAllContactsSelected = view.findViewById(R.id.all_contacts_select); mLinphoneContactsSelected = view.findViewById(R.id.linphone_contacts_select); ImageView backButton = view.findViewById(R.id.back); backButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { ((ChatActivity) getActivity()).goBack(); } }); mNextButton = view.findViewById(R.id.next); mNextButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { if (mChatRoomAddress == null && mChatRoomSubject == null) { mContactsSelectedLayout.removeAllViews(); } else { // Pop the back stack twice so we don't have in stack // Group -> Creation -> Group getFragmentManager().popBackStack(); getFragmentManager().popBackStack(); } ((ChatActivity) getActivity()) .showChatRoomGroupInfo( mChatRoomAddress == null ? null : Factory.instance() .createAddress(mChatRoomAddress), mSearchAdapter.getContactsSelectedList(), mChatRoomSubject, mSecurityToggle.isChecked()); } }); mNextButton.setEnabled(false); mSearchLayout = view.findViewById(R.id.layoutSearchField); ProgressBar contactsFetchInProgress = view.findViewById(R.id.contactsFetchInProgress); contactsFetchInProgress.setVisibility(View.GONE); mSearchAdapter = new SearchContactsAdapter(this, !mCreateGroupChatRoom, mChatRoomEncrypted); mSearchField = view.findViewById(R.id.searchField); mSearchField.setOnQueryTextListener( new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { return true; } @Override public boolean onQueryTextChange(String newText) { mSearchAdapter.searchContacts(newText); return true; } }); mLinphoneContactsToggle = view.findViewById(R.id.layout_linphone_contacts); mAllContactsToggle = view.findViewById(R.id.layout_all_contacts); mSecurityToggle = view.findViewById(R.id.security_toogle); mSecurityToggle.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { setSecurityEnabled(isChecked); } }); ImageView securityToggleOn = view.findViewById(R.id.security_toogle_on); ImageView securityToggleOff = view.findViewById(R.id.security_toogle_off); securityToggleOn.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { setSecurityEnabled(true); } }); securityToggleOff.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { setSecurityEnabled(false); } }); mSecurityToggle.setChecked(mChatRoomEncrypted); mSearchAdapter.setSecurityEnabled(mChatRoomEncrypted); ProxyConfig lpc = LinphoneManager.getCore().getDefaultProxyConfig(); if ((mChatRoomSubject != null && mChatRoomAddress != null) || (lpc == null || lpc.getConferenceFactoryUri() == null)) { mSecurityToggle.setVisibility(View.GONE); securityToggleOn.setVisibility(View.GONE); securityToggleOff.setVisibility(View.GONE); } if (getResources().getBoolean(R.bool.force_end_to_end_encryption_in_chat)) { mSecurityToggle.setChecked(true); mSearchAdapter.setSecurityEnabled(true); mSecurityToggle.setVisibility(View.GONE); securityToggleOn.setVisibility(View.GONE); securityToggleOff.setVisibility(View.GONE); } LinearLayoutManager layoutManager = new LinphoneLinearLayoutManager(getActivity().getApplicationContext()); mContactsList.setAdapter(mSearchAdapter); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration( mContactsList.getContext(), layoutManager.getOrientation()); dividerItemDecoration.setDrawable( getActivity() .getApplicationContext() .getResources() .getDrawable(R.drawable.divider)); mContactsList.addItemDecoration(dividerItemDecoration); mContactsList.setLayoutManager(layoutManager); mOnlyDisplayLinphoneContacts = ContactsManager.getInstance().isLinphoneContactsPrefered() || getResources().getBoolean(R.bool.hide_non_linphone_contacts); if (savedInstanceState != null) { if (mParticipants.isEmpty() && savedInstanceState.getStringArrayList("Participants") != null) { mContactsSelectedLayout.removeAllViews(); // We need to get all contacts not only sip mParticipants = (ArrayList) savedInstanceState.getSerializable("Participants"); } mOnlyDisplayLinphoneContacts = savedInstanceState.getBoolean("onlySipContact", mOnlyDisplayLinphoneContacts); } mChatRoomCreationListener = new ChatRoomListenerStub() { @Override public void onStateChanged(ChatRoom cr, ChatRoom.State newState) { if (newState == ChatRoom.State.Created) { mWaitLayout.setVisibility(View.GONE); // Pop back stack so back button takes to the chat rooms list getFragmentManager().popBackStack(); ((ChatActivity) getActivity()) .showChatRoom( mChatRoom.getLocalAddress(), mChatRoom.getPeerAddress()); } else if (newState == ChatRoom.State.CreationFailed) { mWaitLayout.setVisibility(View.GONE); ((ChatActivity) getActivity()).displayChatRoomError(); Log.e( "[Chat Room Creation] Group chat room for address " + cr.getPeerAddress() + " has failed !"); } } }; return view; } @Override public void onResume() { super.onResume(); ContactsManager.getInstance().addContactsListener(this); updateLayout(); InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(INPUT_METHOD_SERVICE); if (getActivity().getCurrentFocus() != null) { inputMethodManager.hideSoftInputFromWindow( getActivity().getCurrentFocus().getWindowToken(), 0); } } @Override public void onPause() { if (mChatRoom != null) { mChatRoom.removeListener(mChatRoomCreationListener); } ContactsManager.getInstance().removeContactsListener(this); super.onPause(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mSearchAdapter.getContactsSelectedList().size() > 0) { outState.putSerializable("Participants", mSearchAdapter.getContactsSelectedList()); } outState.putBoolean("onlySipContact", mOnlyDisplayLinphoneContacts); } @Override public void onClick(View view) { int id = view.getId(); if (id == R.id.all_contacts) { mOnlyDisplayLinphoneContacts = false; mSearchAdapter.setOnlySipContact(false); mAllContactsSelected.setVisibility(View.VISIBLE); mAllContactsButton.setEnabled(false); mLinphoneContactsButton.setEnabled(true); mLinphoneContactsSelected.setVisibility(View.INVISIBLE); updateList(); resetAndResearch(); } else if (id == R.id.linphone_contacts) { mSearchAdapter.setOnlySipContact(true); mLinphoneContactsSelected.setVisibility(View.VISIBLE); mLinphoneContactsButton.setEnabled(false); mOnlyDisplayLinphoneContacts = true; mAllContactsButton.setEnabled(true); mAllContactsSelected.setVisibility(View.INVISIBLE); updateList(); resetAndResearch(); } else if (id == R.id.contactChatDelete) { ContactAddress ca = (ContactAddress) view.getTag(); addOrRemoveContactFromSelection(ca); } } @Override public void onItemClicked(int position) { SearchResult searchResult = mSearchAdapter.getContacts().get(position); Core core = LinphoneManager.getCore(); ProxyConfig lpc = core.getDefaultProxyConfig(); boolean createEncryptedChatRoom = mSecurityToggle.isChecked(); if (createEncryptedChatRoom && !searchResult.hasCapability(FriendCapability.LimeX3Dh)) { Log.w( "[Chat Room Creation] Contact " + searchResult.getFriend() + " doesn't have LIME X3DH capability !"); return; } else if (mCreateGroupChatRoom && !searchResult.hasCapability(FriendCapability.GroupChat)) { Log.w( "[Chat Room Creation] Contact " + searchResult.getFriend() + " doesn't have group chat capability !"); return; } if (lpc == null || lpc.getConferenceFactoryUri() == null || !mCreateGroupChatRoom) { Address address = searchResult.getAddress(); if (address == null) { Log.w( "[Chat Room Creation] Using search result without an address, trying with phone number..."); address = core.interpretUrl(searchResult.getPhoneNumber()); } if (address == null) { Log.e("[Chat Room Creation] Can't create a chat room without a valid address !"); return; } if (lpc != null && lpc.getIdentityAddress().weakEqual(address)) { Log.e("[Chat Room Creation] Can't create a 1-to-1 chat room with myself !"); return; } if (createEncryptedChatRoom && lpc != null && lpc.getConferenceFactoryUri() != null) { mChatRoom = core.findOneToOneChatRoom(lpc.getIdentityAddress(), address, true); if (mChatRoom != null) { ((ChatActivity) getActivity()) .showChatRoom(mChatRoom.getLocalAddress(), mChatRoom.getPeerAddress()); } else { ChatRoomParams params = core.createDefaultChatRoomParams(); // This will set the backend to FlexisipChat automatically params.enableEncryption(true); params.enableGroup(false); Address[] participants = new Address[1]; participants[0] = address; mChatRoom = core.createChatRoom( params, getString(R.string.dummy_group_chat_subject), participants); if (mChatRoom != null) { mChatRoom.addListener(mChatRoomCreationListener); } else { Log.w("[Chat Room Creation Fragment] createChatRoom returned null..."); mWaitLayout.setVisibility(View.GONE); } } } else { if (lpc != null && lpc.getConferenceFactoryUri() != null && !LinphonePreferences.instance().useBasicChatRoomFor1To1()) { mChatRoom = core.findOneToOneChatRoom(lpc.getIdentityAddress(), address, false); if (mChatRoom == null) { mWaitLayout.setVisibility(View.VISIBLE); ChatRoomParams params = core.createDefaultChatRoomParams(); params.enableEncryption(false); params.enableGroup(false); // We don't want a basic chat room params.setBackend(ChatRoomBackend.FlexisipChat); Address[] participants = new Address[1]; participants[0] = address; mChatRoom = core.createChatRoom( params, getString(R.string.dummy_group_chat_subject), participants); if (mChatRoom != null) { mChatRoom.addListener(mChatRoomCreationListener); } else { Log.w("[Chat Room Creation Fragment] createChatRoom returned null..."); mWaitLayout.setVisibility(View.GONE); } } else { // Pop back stack so back button takes to the chat rooms list getFragmentManager().popBackStack(); ((ChatActivity) getActivity()) .showChatRoom( mChatRoom.getLocalAddress(), mChatRoom.getPeerAddress()); } } else { ChatRoom chatRoom = null; if (lpc != null) chatRoom = core.getChatRoom(address, lpc.getIdentityAddress()); else chatRoom = core.getChatRoom(address); if (chatRoom != null) { // Pop back stack so back button takes to the chat rooms list getFragmentManager().popBackStack(); ((ChatActivity) getActivity()) .showChatRoom( chatRoom.getLocalAddress(), chatRoom.getPeerAddress()); } } } } else { LinphoneContact c = searchResult.getFriend() != null ? (LinphoneContact) searchResult.getFriend().getUserData() : null; if (c == null) { c = ContactsManager.getInstance().findContactFromAddress(searchResult.getAddress()); if (c == null) { c = ContactsManager.getInstance() .findContactFromPhoneNumber(searchResult.getPhoneNumber()); } } addOrRemoveContactFromSelection( new ContactAddress( c, searchResult.getAddress().asStringUriOnly(), searchResult.getPhoneNumber())); } } @Override public void onContactsUpdated() { updateList(); } private void updateLayout() { if (!mParticipants.isEmpty()) { mSearchAdapter.setContactsSelectedList(mParticipants); updateList(); updateListSelected(); } mSearchAdapter.setOnlySipContact(mOnlyDisplayLinphoneContacts); updateList(); displayChatCreation(); } private void setSecurityEnabled(boolean enabled) { mChatRoomEncrypted = enabled; mSecurityToggle.setChecked(mChatRoomEncrypted); mSearchAdapter.setSecurityEnabled(mChatRoomEncrypted); if (enabled) { // Remove all contacts added before LIME switch was set // and that can stay because they don't have the capability mContactsSelectedLayout.removeAllViews(); List toToggle = new ArrayList<>(); for (ContactAddress ca : mSearchAdapter.getContactsSelectedList()) { // If the ContactAddress doesn't have a contact keep it anyway if (ca.getContact() != null && !ca.hasCapability(FriendCapability.LimeX3Dh)) { toToggle.add(ca); } else { if (ca.getView() != null) { mContactsSelectedLayout.addView(ca.getView()); } } } for (ContactAddress ca : toToggle) { mSearchAdapter.toggleContactSelection(ca); } mContactsSelectedLayout.invalidate(); } } private void displayChatCreation() { mNextButton.setVisibility(View.VISIBLE); mNextButton.setEnabled(mSearchAdapter.getContactsSelectedList().size() > 0); mContactsList.setVisibility(View.VISIBLE); mSearchLayout.setVisibility(View.VISIBLE); if (mCreateGroupChatRoom) { mLinphoneContactsToggle.setVisibility(View.GONE); mAllContactsToggle.setVisibility(View.GONE); mContactsSelectLayout.setVisibility(View.VISIBLE); mNextButton.setVisibility(View.VISIBLE); } else { mLinphoneContactsToggle.setVisibility(View.VISIBLE); mAllContactsToggle.setVisibility(View.VISIBLE); mContactsSelectLayout.setVisibility(View.GONE); mNextButton.setVisibility(View.GONE); } if (getResources().getBoolean(R.bool.hide_non_linphone_contacts)) { mLinphoneContactsToggle.setVisibility(View.GONE); mLinphoneContactsButton.setVisibility(View.INVISIBLE); mAllContactsButton.setEnabled(false); mLinphoneContactsButton.setEnabled(false); mOnlyDisplayLinphoneContacts = true; mAllContactsButton.setOnClickListener(null); mLinphoneContactsButton.setOnClickListener(null); mLinphoneContactsSelected.setVisibility(View.INVISIBLE); mLinphoneContactsSelected.setVisibility(View.INVISIBLE); } else { mAllContactsButton.setVisibility(View.VISIBLE); mLinphoneContactsButton.setVisibility(View.VISIBLE); if (mOnlyDisplayLinphoneContacts) { mAllContactsSelected.setVisibility(View.INVISIBLE); mLinphoneContactsSelected.setVisibility(View.VISIBLE); } else { mAllContactsSelected.setVisibility(View.VISIBLE); mLinphoneContactsSelected.setVisibility(View.INVISIBLE); } mAllContactsButton.setEnabled(mOnlyDisplayLinphoneContacts); mLinphoneContactsButton.setEnabled(!mAllContactsButton.isEnabled()); } mContactsSelectedLayout.removeAllViews(); if (mSearchAdapter.getContactsSelectedList().size() > 0) { for (ContactAddress ca : mSearchAdapter.getContactsSelectedList()) { addSelectedContactAddress(ca); } } } private void updateList() { mSearchAdapter.searchContacts(mSearchField.getQuery().toString()); mSearchAdapter.notifyDataSetChanged(); } private void updateListSelected() { if (mSearchAdapter.getContactsSelectedList().size() > 0) { mContactsSelectLayout.invalidate(); mNextButton.setEnabled(true); } else { mNextButton.setEnabled(false); } } private void resetAndResearch() { ContactsManager.getInstance().getMagicSearch().resetSearchCache(); mSearchAdapter.searchContacts(mSearchField.getQuery().toString()); } private void addSelectedContactAddress(ContactAddress ca) { View viewContact = LayoutInflater.from(getActivity()).inflate(R.layout.contact_selected, null); if (ca.getContact() != null) { String name = (ca.getContact().getFullName() != null && !ca.getContact().getFullName().isEmpty()) ? ca.getContact().getFullName() : (ca.getDisplayName() != null) ? ca.getDisplayName() : (ca.getUsername() != null) ? ca.getUsername() : ""; ((TextView) viewContact.findViewById(R.id.sipUri)).setText(name); } else { ((TextView) viewContact.findViewById(R.id.sipUri)) .setText(ca.getAddressAsDisplayableString()); } View removeContact = viewContact.findViewById(R.id.contactChatDelete); removeContact.setTag(ca); removeContact.setOnClickListener(this); viewContact.setOnClickListener(this); ca.setView(viewContact); mContactsSelectedLayout.addView(viewContact); mContactsSelectedLayout.invalidate(); } private void updateContactsClick(ContactAddress ca) { boolean isSelected = mSearchAdapter.toggleContactSelection(ca); if (isSelected) { ContactSelectView csv = new ContactSelectView(getActivity()); csv.setListener(this); csv.setContactName(ca); addSelectedContactAddress(ca); } else { mContactsSelectedLayout.removeAllViews(); for (ContactAddress contactAddress : mSearchAdapter.getContactsSelectedList()) { if (contactAddress.getView() != null) mContactsSelectedLayout.addView(contactAddress.getView()); } } mContactsSelectedLayout.invalidate(); } private void addOrRemoveContactFromSelection(ContactAddress ca) { updateContactsClick(ca); mSearchAdapter.notifyDataSetChanged(); updateListSelected(); } }