Started using friends capabilities for group chat and LIME

This commit is contained in:
Sylvain Berfini 2018-12-13 11:24:25 +01:00
parent 134123358b
commit 4363a0659d
11 changed files with 233 additions and 78 deletions

View file

@ -27,6 +27,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.CompoundButton;
import android.widget.HorizontalScrollView; import android.widget.HorizontalScrollView;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -52,6 +53,7 @@ import org.linphone.core.Address;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomListenerStub; import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.FriendCapability;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.fragments.FragmentsAvailable; import org.linphone.fragments.FragmentsAvailable;
import org.linphone.mediastream.Log; import org.linphone.mediastream.Log;
@ -134,7 +136,11 @@ public class ChatRoomCreationFragment extends Fragment
mSearchAdapter = mSearchAdapter =
new SearchContactsAdapter( new SearchContactsAdapter(
null, mContactsFetchInProgress, this, !mCreateGroupChatRoom); null,
mContactsFetchInProgress,
this,
!mCreateGroupChatRoom,
mChatRoomEncrypted);
mSearchField = view.findViewById(R.id.searchField); mSearchField = view.findViewById(R.id.searchField);
mSearchField.setOnQueryTextListener( mSearchField.setOnQueryTextListener(
@ -155,24 +161,32 @@ public class ChatRoomCreationFragment extends Fragment
mAllContactsToggle = view.findViewById(R.id.layout_all_contacts); mAllContactsToggle = view.findViewById(R.id.layout_all_contacts);
mSecurityToggle = view.findViewById(R.id.security_toogle); mSecurityToggle = view.findViewById(R.id.security_toogle);
mSecurityToggle.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setSecurityEnabled(isChecked);
}
});
mSecurityToggleOn = view.findViewById(R.id.security_toogle_on); mSecurityToggleOn = view.findViewById(R.id.security_toogle_on);
mSecurityToggleOff = view.findViewById(R.id.security_toogle_off); mSecurityToggleOff = view.findViewById(R.id.security_toogle_off);
mSecurityToggleOn.setOnClickListener( mSecurityToggleOn.setOnClickListener(
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
mSecurityToggle.setChecked(true); setSecurityEnabled(true);
} }
}); });
mSecurityToggleOff.setOnClickListener( mSecurityToggleOff.setOnClickListener(
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
mSecurityToggle.setChecked(false); setSecurityEnabled(false);
} }
}); });
mSecurityToggle.setChecked(mChatRoomEncrypted); mSecurityToggle.setChecked(mChatRoomEncrypted);
mSearchAdapter.setSecurityEnabled(mChatRoomEncrypted);
ProxyConfig lpc = LinphoneManager.getLc().getDefaultProxyConfig(); ProxyConfig lpc = LinphoneManager.getLc().getDefaultProxyConfig();
if ((mChatRoomSubject != null && mChatRoomAddress != null) if ((mChatRoomSubject != null && mChatRoomAddress != null)
|| (lpc == null || lpc.getConferenceFactoryUri() == null)) { || (lpc == null || lpc.getConferenceFactoryUri() == null)) {
@ -236,7 +250,7 @@ public class ChatRoomCreationFragment extends Fragment
mWaitLayout.setVisibility(View.GONE); mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance().displayChatRoomError(); LinphoneActivity.instance().displayChatRoomError();
Log.e( Log.e(
"Group chat room for address " "[Chat Room Creation] Group chat room for address "
+ cr.getPeerAddress() + cr.getPeerAddress()
+ " has failed !"); + " has failed !");
} }
@ -286,6 +300,30 @@ public class ChatRoomCreationFragment extends Fragment
super.onPause(); super.onPause();
} }
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
for (ContactAddress ca : mContactsSelected) {
mContactsSelectedLayout.removeAllViews();
if (ca.isSelect() && !ca.hasCapability(FriendCapability.LimeX3Dh)) {
mContactsSelected.remove(getIndexOfCa(ca, mContactsSelected));
}
for (ContactAddress contactAddress : mContactsSelected) {
if (contactAddress.getView() != null) {
mContactsSelectedLayout.addView(contactAddress.getView());
}
}
mSearchAdapter.setContactsSelectedList(mContactsSelected);
mContactsSelectedLayout.invalidate();
}
}
}
private void displayChatCreation() { private void displayChatCreation() {
mNextButton.setVisibility(View.VISIBLE); mNextButton.setVisibility(View.VISIBLE);
mNextButton.setEnabled(mContactsSelected.size() > 0); mNextButton.setEnabled(mContactsSelected.size() > 0);
@ -507,6 +545,21 @@ public class ChatRoomCreationFragment extends Fragment
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
ProxyConfig lpc = lc.getDefaultProxyConfig(); ProxyConfig lpc = lc.getDefaultProxyConfig();
boolean createEncryptedChatRoom = mSecurityToggle.isChecked(); boolean createEncryptedChatRoom = mSecurityToggle.isChecked();
if (createEncryptedChatRoom && !ca.hasCapability(FriendCapability.LimeX3Dh)) {
Log.w(
"[Chat Room Creation] Contact "
+ ca.getContact()
+ " doesn't have LIME X3DH capability !");
return;
} else if (mCreateGroupChatRoom && !ca.hasCapability(FriendCapability.GroupChat)) {
Log.w(
"[Chat Room Creation] Contact "
+ ca.getContact()
+ " doesn't have group chat capability !");
return;
}
if (lpc == null || lpc.getConferenceFactoryUri() == null || !mCreateGroupChatRoom) { if (lpc == null || lpc.getConferenceFactoryUri() == null || !mCreateGroupChatRoom) {
if (createEncryptedChatRoom && lpc != null && lpc.getConferenceFactoryUri() != null) { if (createEncryptedChatRoom && lpc != null && lpc.getConferenceFactoryUri() != null) {
mChatRoom = mChatRoom =

View file

@ -245,6 +245,8 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
contact.createOrUpdateFriendFromNativeContact(); contact.createOrUpdateFriendFromNativeContact();
} }
ContactsManager.getInstance().clearGroupChatContacts();
ContactsManager.getInstance().clearLimeX3dhContacts();
// Now that contact fetching is asynchronous, this is required to ensure // Now that contact fetching is asynchronous, this is required to ensure
// presence subscription event will be sent with all friends // presence subscription event will be sent with all friends
for (FriendList list : LinphoneManager.getLc().getFriendsLists()) { for (FriendList list : LinphoneManager.getLc().getFriendsLists()) {

View file

@ -23,6 +23,7 @@ import android.view.View;
import java.io.Serializable; import java.io.Serializable;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.FriendCapability;
public class ContactAddress implements Serializable { public class ContactAddress implements Serializable {
private LinphoneContact mContact; private LinphoneContact mContact;
@ -118,6 +119,10 @@ public class ContactAddress implements Serializable {
return mIsLinphoneContact; return mIsLinphoneContact;
} }
public boolean hasCapability(FriendCapability capability) {
return mContact.hasFriendCapability(capability);
}
private void init(LinphoneContact c, String a, String pn, boolean isLC) { private void init(LinphoneContact c, String a, String pn, boolean isLC) {
mContact = c; mContact = c;
mAddress = a; mAddress = a;

View file

@ -42,6 +42,7 @@ import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomListenerStub; import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.FriendCapability;
import org.linphone.core.PresenceBasicStatus; import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel; import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
@ -297,9 +298,9 @@ public class ContactDetailsFragment extends Fragment implements OnClickListener
v.findViewById(R.id.contact_chat_secured).setTag(value); v.findViewById(R.id.contact_chat_secured).setTag(value);
} }
if (v.findViewById(R.id.friendLinphone).getVisibility() if (v.findViewById(R.id.friendLinphone).getVisibility() == View.VISIBLE
== View.VISIBLE /* TODO Does mContact have LIME capability ?*/ && lpc.getConferenceFactoryUri() != null
&& lpc.getConferenceFactoryUri() != null) { && mContact.hasFriendCapability(FriendCapability.LimeX3Dh)) {
v.findViewById(R.id.contact_chat_secured).setVisibility(View.VISIBLE); v.findViewById(R.id.contact_chat_secured).setVisibility(View.VISIBLE);
} else { } else {
v.findViewById(R.id.contact_chat_secured).setVisibility(View.GONE); v.findViewById(R.id.contact_chat_secured).setVisibility(View.GONE);

View file

@ -49,6 +49,7 @@ import org.linphone.R;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.Friend; import org.linphone.core.Friend;
import org.linphone.core.FriendCapability;
import org.linphone.core.FriendList; import org.linphone.core.FriendList;
import org.linphone.core.FriendListListener; import org.linphone.core.FriendListListener;
import org.linphone.core.MagicSearch; import org.linphone.core.MagicSearch;
@ -59,7 +60,7 @@ import org.linphone.settings.LinphonePreferences;
public class ContactsManager extends ContentObserver implements FriendListListener { public class ContactsManager extends ContentObserver implements FriendListListener {
private static ContactsManager sInstance; private static ContactsManager sInstance;
private List<LinphoneContact> mContacts, mSipContacts; private List<LinphoneContact> mContacts, mSipContacts, mGroupChatContacts, mLimeX3dhContacts;
private ArrayList<ContactsUpdatedListener> mContactsUpdatedListeners; private ArrayList<ContactsUpdatedListener> mContactsUpdatedListeners;
private MagicSearch mMagicSearch; private MagicSearch mMagicSearch;
private final Bitmap mDefaultAvatar; private final Bitmap mDefaultAvatar;
@ -80,6 +81,9 @@ public class ContactsManager extends ContentObserver implements FriendListListen
mContactsUpdatedListeners = new ArrayList<>(); mContactsUpdatedListeners = new ArrayList<>();
mContacts = new ArrayList<>(); mContacts = new ArrayList<>();
mSipContacts = new ArrayList<>(); mSipContacts = new ArrayList<>();
mGroupChatContacts = new ArrayList<>();
mLimeX3dhContacts = new ArrayList<>();
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) { if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) {
mMagicSearch = LinphoneManager.getLcIfManagerNotDestroyedOrNull().createMagicSearch(); mMagicSearch = LinphoneManager.getLcIfManagerNotDestroyedOrNull().createMagicSearch();
} }
@ -127,6 +131,22 @@ public class ContactsManager extends ContentObserver implements FriendListListen
mSipContacts = c; mSipContacts = c;
} }
public synchronized List<LinphoneContact> getGroupChatContacts() {
return mGroupChatContacts;
}
synchronized void clearGroupChatContacts() {
mGroupChatContacts.clear();
}
public synchronized List<LinphoneContact> getLimeX3dhContacts() {
return mLimeX3dhContacts;
}
synchronized void clearLimeX3dhContacts() {
mLimeX3dhContacts.clear();
}
public void destroy() { public void destroy() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) { if (lc != null) {
@ -379,10 +399,27 @@ public class ContactsManager extends ContentObserver implements FriendListListen
private synchronized boolean refreshSipContact(Friend lf) { private synchronized boolean refreshSipContact(Friend lf) {
LinphoneContact contact = (LinphoneContact) lf.getUserData(); LinphoneContact contact = (LinphoneContact) lf.getUserData();
if (contact != null && !mSipContacts.contains(contact)) { if (contact != null) {
mSipContacts.add(contact); if (lf.hasCapability(FriendCapability.GroupChat)
Collections.sort(mSipContacts); && !mGroupChatContacts.contains(contact)) {
return true; mGroupChatContacts.add(contact);
Collections.sort(mGroupChatContacts);
Log.i("[Contacts Manager] Contact " + contact + " has group chat capability");
// Contact may only have LimeX3DH capability if it already has GroupChat capability
if (lf.hasCapability(FriendCapability.LimeX3Dh)
&& !mLimeX3dhContacts.contains(contact)) {
mLimeX3dhContacts.add(contact);
Collections.sort(mLimeX3dhContacts);
Log.i("[Contacts Manager] Contact " + contact + " has lime x3dh capability");
}
}
if (!mSipContacts.contains(contact)) {
mSipContacts.add(contact);
Collections.sort(mSipContacts);
return true;
}
} }
return false; return false;
} }

View file

@ -28,6 +28,7 @@ import org.linphone.LinphoneManager;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.Friend; import org.linphone.core.Friend;
import org.linphone.core.FriendCapability;
import org.linphone.core.FriendList; import org.linphone.core.FriendList;
import org.linphone.core.PresenceBasicStatus; import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel; import org.linphone.core.PresenceModel;
@ -487,4 +488,10 @@ public class LinphoneContact extends AndroidContact
deleteFriend(); deleteFriend();
} }
} }
public boolean hasFriendCapability(FriendCapability capability) {
if (!isFriend()) return false;
return getFriend().hasCapability(capability);
}
} }

View file

@ -33,16 +33,20 @@ public class SearchContactViewHolder extends RecyclerView.ViewHolder
public final ImageView linphoneContact; public final ImageView linphoneContact;
public final ImageView isSelect; public final ImageView isSelect;
public final RelativeLayout avatarLayout; public final RelativeLayout avatarLayout;
public final View disabled;
private final ClickListener mListener; private final ClickListener mListener;
public SearchContactViewHolder(View view, ClickListener listener) { public SearchContactViewHolder(View view, ClickListener listener) {
super(view); super(view);
name = view.findViewById(R.id.contact_name); name = view.findViewById(R.id.contact_name);
address = view.findViewById(R.id.contact_address); address = view.findViewById(R.id.contact_address);
linphoneContact = view.findViewById(R.id.contact_linphone); linphoneContact = view.findViewById(R.id.contact_linphone);
isSelect = view.findViewById(R.id.contact_is_select); isSelect = view.findViewById(R.id.contact_is_select);
avatarLayout = view.findViewById(R.id.avatar_layout); avatarLayout = view.findViewById(R.id.avatar_layout);
disabled = view.findViewById(R.id.disabled);
mListener = listener; mListener = listener;
view.setOnClickListener(this); view.setOnClickListener(this);
} }

View file

@ -32,6 +32,7 @@ import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.FriendCapability;
import org.linphone.core.PresenceBasicStatus; import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel; import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
@ -47,20 +48,23 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
private final ProgressBar mProgressBar; private final ProgressBar mProgressBar;
private boolean mOnlySipContact = false; private boolean mOnlySipContact = false;
private SearchContactViewHolder.ClickListener mListener; private SearchContactViewHolder.ClickListener mListener;
private final boolean mHideSelectionMark; private final boolean mIsOnlyOnePersonSelection;
private String mPreviousSearch; private String mPreviousSearch;
private boolean mSecurityEnabled;
public SearchContactsAdapter( public SearchContactsAdapter(
List<ContactAddress> contactsList, List<ContactAddress> contactsList,
ProgressBar pB, ProgressBar pB,
SearchContactViewHolder.ClickListener clickListener, SearchContactViewHolder.ClickListener clickListener,
boolean hideSelectionMark) { boolean hideSelectionMark,
mHideSelectionMark = hideSelectionMark; boolean isSecurityEnabled) {
mIsOnlyOnePersonSelection = hideSelectionMark;
mListener = clickListener; mListener = clickListener;
mProgressBar = pB; mProgressBar = pB;
setContactsSelectedList(null); setContactsSelectedList(null);
setContactsList(contactsList); setContactsList(contactsList);
mPreviousSearch = null; mPreviousSearch = null;
mSecurityEnabled = isSecurityEnabled;
} }
public List<ContactAddress> getContacts() { public List<ContactAddress> getContacts() {
@ -71,6 +75,11 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
mOnlySipContact = enable; mOnlySipContact = enable;
} }
public void setSecurityEnabled(boolean enable) {
mSecurityEnabled = enable;
notifyDataSetChanged();
}
@NonNull @NonNull
@Override @Override
public SearchContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public SearchContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -113,12 +122,19 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
holder.name.setVisibility(View.GONE); holder.name.setVisibility(View.GONE);
} }
holder.disabled.setVisibility(View.GONE);
if (c != null) { if (c != null) {
if (c.getFullName() == null && c.getFirstName() == null && c.getLastName() == null) { if (c.getFullName() == null && c.getFirstName() == null && c.getLastName() == null) {
c.setFullName(holder.name.getText().toString()); c.setFullName(holder.name.getText().toString());
} }
ContactAvatar.displayAvatar(c, holder.avatarLayout); ContactAvatar.displayAvatar(
// TODO get if contact has security capabilities c, c.hasFriendCapability(FriendCapability.LimeX3Dh), holder.avatarLayout);
if ((!mIsOnlyOnePersonSelection && !c.hasFriendCapability(FriendCapability.GroupChat))
|| (mSecurityEnabled && !c.hasFriendCapability(FriendCapability.LimeX3Dh))) {
// Disable row, contact doesn't have the required capabilities
holder.disabled.setVisibility(View.VISIBLE);
}
} else { } else {
ContactAvatar.displayAvatar(holder.name.getText().toString(), holder.avatarLayout); ContactAvatar.displayAvatar(holder.name.getText().toString(), holder.avatarLayout);
} }
@ -137,7 +153,7 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
} else { } else {
holder.isSelect.setVisibility(View.INVISIBLE); holder.isSelect.setVisibility(View.INVISIBLE);
} }
if (mHideSelectionMark) { if (mIsOnlyOnePersonSelection) {
holder.isSelect.setVisibility(View.GONE); holder.isSelect.setVisibility(View.GONE);
} }
} }

View file

@ -85,6 +85,16 @@ public class ContactAvatar {
} }
} }
private static void showHasLimeX3dhCapability(View v) {
ContactAvatarHolder holder = new ContactAvatarHolder(v);
if (holder.securityLevel != null) {
holder.securityLevel.setVisibility(View.VISIBLE);
holder.securityLevel.setImageResource(R.drawable.security_toogle_icon_green);
} else {
holder.securityLevel.setVisibility(View.GONE);
}
}
public static void setAvatarMask(View v, int resourceId) { public static void setAvatarMask(View v, int resourceId) {
ContactAvatarHolder holder = new ContactAvatarHolder(v); ContactAvatarHolder holder = new ContactAvatarHolder(v);
holder.avatarMask.setImageResource(resourceId); holder.avatarMask.setImageResource(resourceId);
@ -151,6 +161,14 @@ public class ContactAvatar {
holder.securityLevel.setVisibility(View.GONE); holder.securityLevel.setVisibility(View.GONE);
} }
public static void displayAvatar(
LinphoneContact contact, boolean hasLimeX3dhCapability, View v) {
displayAvatar(contact, v);
if (hasLimeX3dhCapability) {
showHasLimeX3dhCapability(v);
}
}
public static void displayAvatar( public static void displayAvatar(
LinphoneContact contact, ChatRoomSecurityLevel securityLevel, View v) { LinphoneContact contact, ChatRoomSecurityLevel securityLevel, View v) {
displayAvatar(contact, v); displayAvatar(contact, v);

View file

@ -1,80 +1,91 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="60dp">
android:background="@drawable/list_selector"
android:gravity="center_vertical"
android:orientation="vertical"
android:padding="5dp">
<RelativeLayout <RelativeLayout
android:id="@+id/avatar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center">
<include layout="@layout/contact_avatar" />
</RelativeLayout>
<LinearLayout
android:id="@+id/layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="70dp"
android:layout_toRightOf="@id/avatar"
android:background="@drawable/list_selector" android:background="@drawable/list_selector"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="vertical"> android:orientation="vertical"
android:padding="5dp">
<TextView <RelativeLayout
android:id="@+id/contact_name" android:id="@+id/avatar"
style="@style/font6"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center">
<include layout="@layout/contact_avatar" />
</RelativeLayout>
<LinearLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="top|left" android:layout_alignParentTop="true"
android:lines="1" /> android:layout_alignParentBottom="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="70dp"
android:layout_toRightOf="@id/avatar"
android:background="@drawable/list_selector"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView <TextView
android:id="@+id/contact_address" android:id="@+id/contact_name"
style="@style/font2" style="@style/font6"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:gravity="top|left"
android:gravity="bottom|left" android:lines="1" />
android:lines="1" />
</LinearLayout> <TextView
android:id="@+id/contact_address"
style="@style/font2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="bottom|left"
android:lines="1" />
<RelativeLayout </LinearLayout>
android:layout_width="match_parent"
android:layout_height="match_parent" <RelativeLayout
android:layout_toLeftOf="@+id/contact_is_select"> android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@+id/contact_is_select">
<ImageView
android:id="@+id/contact_linphone"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_gravity="right"
android:paddingRight="10dp"
android:src="@drawable/linphone_user" />
</RelativeLayout>
<ImageView <ImageView
android:id="@+id/contact_linphone" android:id="@+id/contact_is_select"
android:layout_width="30dp" android:layout_width="40dp"
android:layout_height="30dp" android:layout_height="40dp"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerInParent="true"
android:layout_gravity="right" android:layout_gravity="right"
android:paddingRight="10dp" android:paddingRight="20dp"
android:src="@drawable/linphone_user" /> android:src="@drawable/check_selected"
android:visibility="invisible" />
</RelativeLayout> </RelativeLayout>
<ImageView <View
android:id="@+id/contact_is_select" android:id="@+id/disabled"
android:layout_width="40dp" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="match_parent"
android:layout_alignParentRight="true" android:background="@color/contact_disabled"/>
android:layout_centerInParent="true" </FrameLayout>
android:layout_gravity="right"
android:paddingRight="20dp"
android:src="@drawable/check_selected"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -23,4 +23,5 @@
<color name="transparent">#00000000</color> <color name="transparent">#00000000</color>
<color name="linphone_launcher_icon_background">@color/colorA</color> <color name="linphone_launcher_icon_background">@color/colorA</color>
<color name="contact_disabled">#99ffffff</color>
</resources> </resources>