From a4d2ed86bb69aa440aaa3121d5fd56c3ba7ebdc9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 19 Jul 2012 15:17:50 +0200 Subject: [PATCH] Big improvment of contacts --- res/layout/contacts_list.xml | 18 ++- src/org/linphone/Contact.java | 2 +- src/org/linphone/ContactFragment.java | 12 +- src/org/linphone/ContactsFragment.java | 100 ++++---------- src/org/linphone/LinphoneActivity.java | 51 +++++++ src/org/linphone/LinphoneUtils.java | 4 + .../linphone/compatibility/ApiFivePlus.java | 130 +++++++++++++++--- .../linphone/compatibility/Compatibility.java | 42 +++--- 8 files changed, 232 insertions(+), 127 deletions(-) diff --git a/res/layout/contacts_list.xml b/res/layout/contacts_list.xml index 38e2c1b3e..f59bb8835 100644 --- a/res/layout/contacts_list.xml +++ b/res/layout/contacts_list.xml @@ -37,13 +37,19 @@ - + android:layout_weight="0.13"> + + + + \ No newline at end of file diff --git a/src/org/linphone/Contact.java b/src/org/linphone/Contact.java index 55d16a684..b762060fa 100644 --- a/src/org/linphone/Contact.java +++ b/src/org/linphone/Contact.java @@ -31,7 +31,7 @@ public class Contact implements Serializable { private String id; private String name; - private transient Uri photoUri; + private Uri photoUri; private transient Bitmap photo; private List numerosOrAddresses; diff --git a/src/org/linphone/ContactFragment.java b/src/org/linphone/ContactFragment.java index 90af9eccc..17759c1a2 100644 --- a/src/org/linphone/ContactFragment.java +++ b/src/org/linphone/ContactFragment.java @@ -17,6 +17,11 @@ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +import java.io.InputStream; + +import org.linphone.compatibility.Compatibility; + +import android.graphics.BitmapFactory; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -40,8 +45,11 @@ public class ContactFragment extends Fragment { View view = inflater.inflate(R.layout.contact, container, false); ImageView contactPicture = (ImageView) view.findViewById(R.id.contactPicture); - if (contact.getPhoto() != null) { - LinphoneUtils.setImagePictureFromUri(view.getContext(), contactPicture, contact.getPhotoUri(), R.drawable.unknown_small); + if (contact.getPhotoUri() != null) { + InputStream input = Compatibility.getContactPictureInputStream(getActivity().getContentResolver(), contact.getID()); + contactPicture.setImageBitmap(BitmapFactory.decodeStream(input)); + } else { + contactPicture.setImageResource(R.drawable.unknown_small); } chatListener = getChatListener(); diff --git a/src/org/linphone/ContactsFragment.java b/src/org/linphone/ContactsFragment.java index 59af02d20..84981f9cc 100644 --- a/src/org/linphone/ContactsFragment.java +++ b/src/org/linphone/ContactsFragment.java @@ -17,8 +17,6 @@ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -import java.io.InputStream; -import java.util.ArrayList; import java.util.List; import org.linphone.compatibility.Compatibility; @@ -27,11 +25,8 @@ import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.net.Uri; import android.os.Bundle; -import android.provider.ContactsContract; import android.support.v4.app.Fragment; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -53,11 +48,8 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte private LayoutInflater mInflater; private ListView contactsList; private ImageView allContacts, linphoneContacts, newContact; - private boolean onlyDisplayLinphoneCalls; + private boolean onlyDisplayLinphoneContacts; private int lastKnownPosition; - private Cursor cursor; - private List contacts; - private Thread contactsHandler; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -72,12 +64,13 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte allContacts.setOnClickListener(this); linphoneContacts = (ImageView) view.findViewById(R.id.linphoneContacts); linphoneContacts.setOnClickListener(this); - allContacts.setEnabled(false); - onlyDisplayLinphoneCalls = false; newContact = (ImageView) view.findViewById(R.id.newContact); newContact.setOnClickListener(this); newContact.setEnabled(!LinphoneActivity.instance().isInCallLayout()); + allContacts.setEnabled(onlyDisplayLinphoneContacts); + linphoneContacts.setEnabled(!allContacts.isEnabled()); + return view; } @@ -88,12 +81,14 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte if (id == R.id.allContacts) { allContacts.setEnabled(false); linphoneContacts.setEnabled(true); - onlyDisplayLinphoneCalls = false; + onlyDisplayLinphoneContacts = false; + contactsList.setAdapter(new ContactsListAdapter(LinphoneActivity.instance().getAllContacts(), LinphoneActivity.instance().getAllContactsCursor())); } else if (id == R.id.linphoneContacts) { allContacts.setEnabled(true); linphoneContacts.setEnabled(false); - onlyDisplayLinphoneCalls = true; + onlyDisplayLinphoneContacts = true; + contactsList.setAdapter(new ContactsListAdapter(LinphoneActivity.instance().getSIPContacts(), LinphoneActivity.instance().getSIPContactsCursor())); } else if (id == R.id.newContact) { Intent intent = Compatibility.prepareAddContactIntent(null, null); @@ -107,24 +102,6 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte LinphoneActivity.instance().displayContact((Contact) adapter.getItemAtPosition(position)); } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - cursor = Compatibility.getContactsCursor(getActivity().getContentResolver()); - contactsHandler = new Thread(new Runnable() { - @Override - public void run() { - contacts = new ArrayList(); - for (int i = 0; i < cursor.getCount(); i++) { - Contact contact = getContact(i); - contacts.add(contact); - } - } - }); - contactsHandler.start(); - } - @Override public void onResume() { super.onResume(); @@ -133,56 +110,30 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte } if (contactsList.getAdapter() == null) { - contactsList.setAdapter(new ContactsListAdapter()); + if (onlyDisplayLinphoneContacts) { + contactsList.setAdapter(new ContactsListAdapter(LinphoneActivity.instance().getSIPContacts(), LinphoneActivity.instance().getSIPContactsCursor())); + } else { + contactsList.setAdapter(new ContactsListAdapter(LinphoneActivity.instance().getAllContacts(), LinphoneActivity.instance().getAllContactsCursor())); + } contactsList.setFastScrollEnabled(true); } contactsList.setSelectionFromTop(lastKnownPosition, 0); } - @Override - public void onPause() { - contactsHandler.interrupt(); - super.onPause(); - } - - private Contact getContact(int position) { - try { - cursor.moveToFirst(); - boolean success = cursor.move(position); - if (!success) - return null; - - String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); - String name = Compatibility.getContactDisplayName(cursor); - Uri photo = Compatibility.getContactPictureUri(cursor, id); - InputStream input = Compatibility.getContactPictureInputStream(getActivity().getContentResolver(), id); - - Contact contact; - if (input == null) { - contact = new Contact(id, name); - } - else { - contact = new Contact(id, name, photo, BitmapFactory.decodeStream(input)); - } - - contact.setNumerosOrAddresses(Compatibility.extractContactNumbersAndAddresses(contact.getID(), getActivity().getContentResolver())); - - return contact; - } catch (Exception e) { - - } - return null; - } - class ContactsListAdapter extends BaseAdapter implements SectionIndexer { private AlphabetIndexer indexer; private int margin; private Bitmap bitmapUnknown; + private List contacts; + private Cursor cursor; - ContactsListAdapter() { + ContactsListAdapter(List contactsList, Cursor c) { + contacts = contactsList; + cursor = c; + indexer = new AlphabetIndexer(cursor, Compatibility.getCursorDisplayNameColumnIndex(cursor), " ABCDEFGHIJKLMNOPQRSTUVWXYZ"); - margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()); + margin = LinphoneUtils.pixelsToDpi(getResources(), 10); bitmapUnknown = BitmapFactory.decodeResource(getResources(), R.drawable.unknown_small); } @@ -192,7 +143,7 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte public Object getItem(int position) { if (position >= contacts.size()) { - return getContact(position); + return Compatibility.getContact(getActivity().getContentResolver(), cursor, position); } else { return contacts.get(position); } @@ -204,6 +155,10 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte public View getView(int position, View convertView, ViewGroup parent) { View view = null; + Contact contact = null; + do { + contact = (Contact) getItem(position); + } while (contact == null); if (convertView != null) { view = convertView; @@ -211,11 +166,6 @@ public class ContactsFragment extends Fragment implements OnClickListener, OnIte view = mInflater.inflate(R.layout.contact_cell, parent, false); } - Contact contact = null; - do { - contact = (Contact) getItem(position); - } while (contact == null); - TextView name = (TextView) view.findViewById(R.id.name); name.setText(contact.getName()); diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index eaf4a7b66..f4473b7f3 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -44,6 +44,7 @@ import org.linphone.ui.AddressText; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -82,6 +83,8 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene private ChatStorage chatStorage; private Handler mHandler = new Handler(); private boolean isInCallLayout = false; + private List contactList, sipContactList; + private Cursor contactCursor, sipContactCursor; static final boolean isInstanciated() { return instance != null; @@ -104,6 +107,8 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene return; } + prepareContactsInBackground(); + boolean useFirstLoginActivity = getResources().getBoolean(R.bool.useFirstLoginActivity); SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); if (useFirstLoginActivity && !pref.getBoolean(getString(R.string.first_launch_suceeded_once_key), false)) { @@ -554,6 +559,52 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene } + public List getAllContacts() { + return contactList; + } + + public List getSIPContacts() { + return sipContactList; + } + + public Cursor getAllContactsCursor() { + return contactCursor; + } + + public Cursor getSIPContactsCursor() { + return sipContactCursor; + } + + private void prepareContactsInBackground() { + contactCursor = Compatibility.getContactsCursor(getContentResolver()); + sipContactCursor = Compatibility.getSIPContactsCursor(getContentResolver()); + contactList = new ArrayList(); + sipContactList = new ArrayList(); + Thread sipContactsHandler = new Thread(new Runnable() { + @Override + public void run() { + + for (int i = 0; i < sipContactCursor.getCount(); i++) { + Contact contact = Compatibility.getContact(getContentResolver(), sipContactCursor, i); + sipContactList.add(contact); + } + } + }); + sipContactsHandler.start(); + + Thread contactsHandler = new Thread(new Runnable() { + @Override + public void run() { + + for (int i = 0; i < contactCursor.getCount(); i++) { + Contact contact = Compatibility.getContact(getContentResolver(), contactCursor, i); + contactList.add(contact); + } + } + }); + contactsHandler.start(); + } + private void initInCallMenuLayout() { isInCallLayout = true; selectMenu(FragmentsAvailable.DIALER); diff --git a/src/org/linphone/LinphoneUtils.java b/src/org/linphone/LinphoneUtils.java index de521ac41..998172ef2 100644 --- a/src/org/linphone/LinphoneUtils.java +++ b/src/org/linphone/LinphoneUtils.java @@ -68,6 +68,10 @@ public final class LinphoneUtils { return numberOrAddress != null && numberOrAddress.matches("^(sip:)?[a-z0-9]+([_\\.-][a-z0-9]+)*@([a-z0-9]+([\\.-][a-z0-9]+)*)+\\.[a-z]{2,}$"); } + public static boolean isStrictSipAddress(String numberOrAddress) { + return numberOrAddress != null && numberOrAddress.matches("^sip:[a-z0-9]+([_\\.-][a-z0-9]+)*@([a-z0-9]+([\\.-][a-z0-9]+)*)+\\.[a-z]{2,}$"); + } + public static String getUsernameFromAddress(String address) { if (address.contains("sip:")) address = address.replace("sip:", ""); diff --git a/src/org/linphone/compatibility/ApiFivePlus.java b/src/org/linphone/compatibility/ApiFivePlus.java index f62b6e54e..583427882 100644 --- a/src/org/linphone/compatibility/ApiFivePlus.java +++ b/src/org/linphone/compatibility/ApiFivePlus.java @@ -2,8 +2,11 @@ package org.linphone.compatibility; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import org.linphone.Contact; import org.linphone.mediastream.Version; import android.annotation.TargetApi; @@ -13,12 +16,15 @@ import android.content.ContentUris; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; +import android.database.MatrixCursor; +import android.graphics.BitmapFactory; import android.net.Uri; import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.Contacts.Data; +import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Intents.Insert; /* @@ -55,7 +61,7 @@ public class ApiFivePlus { if (Version.sdkAboveOrEqual(Version.API09_GINGERBREAD_23)) { ArrayList data = new ArrayList(); ContentValues sipAddressRow = new ContentValues(); - sipAddressRow.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); + sipAddressRow.put(Contacts.Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); sipAddressRow.put(SipAddress.SIP_ADDRESS, sipUri); data.add(sipAddressRow); intent.putParcelableArrayListExtra(Insert.DATA, data); @@ -71,15 +77,15 @@ public class ApiFivePlus { public static List extractContactNumbersAndAddresses(String id, ContentResolver cr) { List list = new ArrayList(); - Uri uri = ContactsContract.Data.CONTENT_URI; + Uri uri = Data.CONTENT_URI; String[] projection = {ContactsContract.CommonDataKinds.Im.DATA}; // SIP addresses if (Version.sdkAboveOrEqual(Version.API09_GINGERBREAD_23)) { String selection = new StringBuilder() - .append(ContactsContract.Data.CONTACT_ID) + .append(Data.CONTACT_ID) .append(" = ? AND ") - .append(ContactsContract.Data.MIMETYPE) + .append(Data.MIMETYPE) .append(" = '") .append(ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE) .append("'") @@ -94,8 +100,8 @@ public class ApiFivePlus { c.close(); } else { String selection = new StringBuilder() - .append(ContactsContract.Data.CONTACT_ID).append(" = ? AND ") - .append(ContactsContract.Data.MIMETYPE).append(" = '") + .append(Data.CONTACT_ID).append(" = ? AND ") + .append(Data.MIMETYPE).append(" = '") .append(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) .append("' AND lower(") .append(ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL) @@ -111,7 +117,7 @@ public class ApiFivePlus { } // Phone Numbers - Cursor c = cr.query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = " + id, null, null); + Cursor c = cr.query(Phone.CONTENT_URI, new String[] { Phone.NUMBER }, Phone.CONTACT_ID + " = " + id, null, null); while (c.moveToNext()) { String number = c.getString(c.getColumnIndex(Phone.NUMBER)); list.add(number); @@ -121,25 +127,105 @@ public class ApiFivePlus { return list; } + @TargetApi(11) public static Cursor getContactsCursor(ContentResolver cr) { - return cr.query(ContactsContract.Contacts.CONTENT_URI, null, ContactsContract.Contacts.DISPLAY_NAME + " IS NOT NULL", null, ContactsContract.Contacts.DISPLAY_NAME + " ASC"); + String req = Data.MIMETYPE + " = '" + CommonDataKinds.Phone.CONTENT_ITEM_TYPE + + "' AND " + CommonDataKinds.Phone.NUMBER + " IS NOT NULL"; + + if (Version.sdkAboveOrEqual(Version.API09_GINGERBREAD_23)) { + req += " OR (" + Data.MIMETYPE + " = '" + CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + + "' AND " + ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS + " IS NOT NULL)"; + } else { + req += " OR (" + Contacts.Data.MIMETYPE + " = '" + CommonDataKinds.Im.CONTENT_ITEM_TYPE + + " AND lower(" + CommonDataKinds.Im.CUSTOM_PROTOCOL + ") = 'sip')"; + } + + return getGeneralContactCursor(cr, req); + } + + public static Cursor getSIPContactsCursor(ContentResolver cr) { + String req = null; + if (Version.sdkAboveOrEqual(Version.API09_GINGERBREAD_23)) { + req = Data.MIMETYPE + " = '" + CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + + "' AND " + ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS + " IS NOT NULL"; + } else { + req = Contacts.Data.MIMETYPE + " = '" + CommonDataKinds.Im.CONTENT_ITEM_TYPE + + " AND lower(" + CommonDataKinds.Im.CUSTOM_PROTOCOL + ") = 'sip'"; + } + + return getGeneralContactCursor(cr, req); } - public static String getContactDisplayName(Cursor cursor) { - return cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); - } - - public static Uri getContactPictureUri(Cursor cursor, String id) { - Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(id)); - return Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); - } - - public static InputStream getContactPictureInputStream(ContentResolver cr, String id) { - Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(id)); - return ContactsContract.Contacts.openContactPhotoInputStream(cr, person); + private static Cursor getGeneralContactCursor(ContentResolver cr, String select) { + + String[] projection = new String[] { Data.CONTACT_ID, Data.DISPLAY_NAME }; + + String query = Data.DISPLAY_NAME + " IS NOT NULL AND (" + select + ")"; + Cursor cursor = cr.query(Data.CONTENT_URI, projection, query, null, Data.DISPLAY_NAME + " ASC"); + + MatrixCursor result = new MatrixCursor(cursor.getColumnNames()); + Set groupBy = new HashSet(); + while (cursor.moveToNext()) { + String name = cursor.getString(getCursorDisplayNameColumnIndex(cursor)); + if (!groupBy.contains(name)) { + groupBy.add(name); + Object[] newRow = new Object[cursor.getColumnCount()]; + + int contactID = cursor.getColumnIndex(Data.CONTACT_ID); + int displayName = cursor.getColumnIndex(Data.DISPLAY_NAME); + + newRow[contactID] = cursor.getString(contactID); + newRow[displayName] = cursor.getString(displayName); + + result.addRow(newRow); + } + } + return result; } public static int getCursorDisplayNameColumnIndex(Cursor cursor) { - return cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); + return cursor.getColumnIndex(Data.DISPLAY_NAME); + } + + public static Contact getContact(ContentResolver cr, Cursor cursor, int position) { + try { + cursor.moveToFirst(); + boolean success = cursor.move(position); + if (!success) + return null; + + String id = cursor.getString(cursor.getColumnIndex(Data.CONTACT_ID)); + String name = getContactDisplayName(cursor); + Uri photo = getContactPictureUri(id); + InputStream input = getContactPictureInputStream(cr, id); + + Contact contact; + if (input == null) { + contact = new Contact(id, name); + } + else { + contact = new Contact(id, name, photo, BitmapFactory.decodeStream(input)); + } + + contact.setNumerosOrAddresses(Compatibility.extractContactNumbersAndAddresses(contact.getID(), cr)); + + return contact; + } catch (Exception e) { + + } + return null; + } + + public static InputStream getContactPictureInputStream(ContentResolver cr, String id) { + Uri person = getContactPictureUri(id); + return Contacts.openContactPhotoInputStream(cr, person); + } + + private static String getContactDisplayName(Cursor cursor) { + return cursor.getString(cursor.getColumnIndex(Data.DISPLAY_NAME)); + } + + private static Uri getContactPictureUri(String id) { + return ContentUris.withAppendedId(Contacts.CONTENT_URI, Long.parseLong(id)); } } diff --git a/src/org/linphone/compatibility/Compatibility.java b/src/org/linphone/compatibility/Compatibility.java index fe1fdea4f..b3554ba07 100644 --- a/src/org/linphone/compatibility/Compatibility.java +++ b/src/org/linphone/compatibility/Compatibility.java @@ -20,13 +20,13 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import java.io.InputStream; import java.util.List; +import org.linphone.Contact; import org.linphone.mediastream.Version; import android.app.Activity; import android.content.ContentResolver; import android.content.Intent; import android.database.Cursor; -import android.net.Uri; /** * @author Sylvain Berfini */ @@ -64,27 +64,9 @@ public class Compatibility { return null; } - public static String getContactDisplayName(Cursor cursor) { + public static Cursor getSIPContactsCursor(ContentResolver cr) { if (Version.sdkAboveOrEqual(5)) { - return ApiFivePlus.getContactDisplayName(cursor); - } else { - //TODO - } - return null; - } - - public static Uri getContactPictureUri(Cursor cursor, String id) { - if (Version.sdkAboveOrEqual(5)) { - return ApiFivePlus.getContactPictureUri(cursor, id); - } else { - //TODO - } - return null; - } - - public static InputStream getContactPictureInputStream(ContentResolver cr, String id) { - if (Version.sdkAboveOrEqual(5)) { - return ApiFivePlus.getContactPictureInputStream(cr, id); + return ApiFivePlus.getSIPContactsCursor(cr); } else { //TODO } @@ -99,4 +81,22 @@ public class Compatibility { } return -1; } + + public static Contact getContact(ContentResolver cr, Cursor cursor, int position) { + if (Version.sdkAboveOrEqual(5)) { + return ApiFivePlus.getContact(cr, cursor, position); + } else { + //TODO + } + return null; + } + + public static InputStream getContactPictureInputStream(ContentResolver cr, String id) { + if (Version.sdkAboveOrEqual(5)) { + return ApiFivePlus.getContactPictureInputStream(cr, id); + } else { + //TODO + } + return null; + } }