Huge improvement on contacts loading time

This commit is contained in:
Sylvain Berfini 2017-01-19 15:47:38 +01:00
parent d2fad6fcf1
commit 06da98f191
4 changed files with 126 additions and 67 deletions

View file

@ -183,7 +183,7 @@ public class ContactDetailsFragment extends Fragment implements OnClickListener
LinphoneActivity.instance().selectMenu(FragmentsAvailable.CONTACT_DETAIL); LinphoneActivity.instance().selectMenu(FragmentsAvailable.CONTACT_DETAIL);
LinphoneActivity.instance().hideTabBar(false); LinphoneActivity.instance().hideTabBar(false);
} }
contact.minimalRefresh(true); contact.refresh();
displayContact(inflater, view); displayContact(inflater, view);
} }

View file

@ -68,6 +68,7 @@ public class ContactsManager extends ContentObserver {
private Context context; private Context context;
private ContactsFetchTask contactsFetchTask; private ContactsFetchTask contactsFetchTask;
private HashMap<String, LinphoneContact> contactsCache; private HashMap<String, LinphoneContact> contactsCache;
private HashMap<String, LinphoneContact> androidContactsCache;
private LinphoneContact contactNotFound; private LinphoneContact contactNotFound;
private Bitmap defaultAvatar; private Bitmap defaultAvatar;
@ -91,6 +92,7 @@ public class ContactsManager extends ContentObserver {
defaultAvatar = BitmapFactory.decodeResource(LinphoneService.instance().getResources(), R.drawable.avatar); defaultAvatar = BitmapFactory.decodeResource(LinphoneService.instance().getResources(), R.drawable.avatar);
contactNotFound = new LinphoneContact(); contactNotFound = new LinphoneContact();
contactsCache = new HashMap<String, LinphoneContact>(); contactsCache = new HashMap<String, LinphoneContact>();
androidContactsCache = new HashMap<String, LinphoneContact>();
contactsUpdatedListeners = new ArrayList<ContactsUpdatedListener>(); contactsUpdatedListeners = new ArrayList<ContactsUpdatedListener>();
contacts = new ArrayList<LinphoneContact>(); contacts = new ArrayList<LinphoneContact>();
sipContacts = new ArrayList<LinphoneContact>(); sipContacts = new ArrayList<LinphoneContact>();
@ -292,13 +294,17 @@ public class ContactsManager extends ContentObserver {
sipContacts = c; sipContacts = c;
} }
public synchronized void refreshSipContacts() { public synchronized void refreshSipContact(LinphoneFriend lf) {
for (LinphoneContact contact : contacts) { for (LinphoneContact contact : contacts) {
if (contact.getLinphoneFriend() == lf) {
if (contact.isInLinphoneFriendList() && !sipContacts.contains(contact)) { if (contact.isInLinphoneFriendList() && !sipContacts.contains(contact)) {
sipContacts.add(contact); sipContacts.add(contact);
} }
break;
}
} }
Collections.sort(sipContacts); Collections.sort(sipContacts);
for (ContactsUpdatedListener listener : contactsUpdatedListeners) { for (ContactsUpdatedListener listener : contactsUpdatedListeners) {
listener.onContactsUpdated(); listener.onContactsUpdated();
} }
@ -327,6 +333,7 @@ public class ContactsManager extends ContentObserver {
List<LinphoneContact> contacts = new ArrayList<LinphoneContact>(); List<LinphoneContact> contacts = new ArrayList<LinphoneContact>();
List<LinphoneContact> sipContacts = new ArrayList<LinphoneContact>(); List<LinphoneContact> sipContacts = new ArrayList<LinphoneContact>();
Date contactsTime = new Date(); Date contactsTime = new Date();
androidContactsCache.clear();
//We need to check sometimes to know if Linphone was destroyed //We need to check sometimes to know if Linphone was destroyed
if (this.isCancelled()) { if (this.isCancelled()) {
@ -340,12 +347,31 @@ public class ContactsManager extends ContentObserver {
String id = c.getString(c.getColumnIndex(Data.CONTACT_ID)); String id = c.getString(c.getColumnIndex(Data.CONTACT_ID));
String displayName = c.getString(c.getColumnIndex(Data.DISPLAY_NAME_PRIMARY)); String displayName = c.getString(c.getColumnIndex(Data.DISPLAY_NAME_PRIMARY));
LinphoneContact contact = new LinphoneContact(); LinphoneContact contact = new LinphoneContact();
contact.setFullName(displayName); contact.setFullName(displayName);
contact.setAndroidId(id); contact.setAndroidId(id);
/*contact.getAndroidIds();*/
contacts.add(contact); contacts.add(contact);
androidContactsCache.put(id, contact);
} }
c.close(); c.close();
} }
boolean isOrgVisible = LinphoneManager.getInstance().getContext().getResources().getBoolean(R.bool.display_contact_organization);
if (isOrgVisible) {
c = getOrganizationCursor(contentResolver);
if (c != null) {
while (c.moveToNext()) {
String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
String org = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Organization.COMPANY));
LinphoneContact contact = androidContactsCache.get(id);
if (contact != null) {
contact.setOrganization(org);
}
}
c.close();
}
}
} else { } else {
Log.w("[Permission] Read contacts permission wasn't granted, only fetch LinphoneFriends"); Log.w("[Permission] Read contacts permission wasn't granted, only fetch LinphoneFriends");
} }
@ -354,7 +380,7 @@ public class ContactsManager extends ContentObserver {
TimeUnit.MILLISECONDS.toMinutes(timeElapsed), TimeUnit.MILLISECONDS.toMinutes(timeElapsed),
TimeUnit.MILLISECONDS.toSeconds(timeElapsed) - TimeUnit.MILLISECONDS.toSeconds(timeElapsed) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeElapsed))); TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeElapsed)));
Log.i("[ContactsManager] got " + contacts.size() + " contacts names and pictures in " + time); Log.i("[ContactsManager] Step 1 for " + contacts.size() + " contacts executed in " + time);
//We need to check sometimes to know if Linphone was destroyed //We need to check sometimes to know if Linphone was destroyed
if (this.isCancelled()) { if (this.isCancelled()) {
@ -382,6 +408,10 @@ public class ContactsManager extends ContentObserver {
// Refkey not null but no contact access => can't link it to native contact so display it on is own // Refkey not null but no contact access => can't link it to native contact so display it on is own
LinphoneContact contact = new LinphoneContact(); LinphoneContact contact = new LinphoneContact();
contact.setFriend(friend); contact.setFriend(friend);
contact.refresh();
if (contact.hasAddress()) {
sipContacts.add(contact);
}
contacts.add(contact); contacts.add(contact);
} }
} else { } else {
@ -392,66 +422,78 @@ public class ContactsManager extends ContentObserver {
// No refkey so it's a standalone contact // No refkey so it's a standalone contact
LinphoneContact contact = new LinphoneContact(); LinphoneContact contact = new LinphoneContact();
contact.setFriend(friend); contact.setFriend(friend);
contact.refresh();
if (contact.hasAddress()) {
sipContacts.add(contact);
}
contacts.add(contact); contacts.add(contact);
} }
} }
} }
int i = 0;
for (LinphoneContact contact : contacts) {
//We need to check sometimes to know if Linphone was destroyed
if (this.isCancelled()) {
return null;
}
// This will only get name & picture informations to be able to quickly display contacts list
contact.minimalRefresh();
i++;
if (contact.hasAddress()) {
sipContacts.add(contact);
}
if (i == CONTACTS_STEP) {
i = 0;
publishProgress(new ContactsLists(contacts, sipContacts));
}
}
Collections.sort(contacts); Collections.sort(contacts);
Collections.sort(sipContacts); Collections.sort(sipContacts);
publishProgress(new ContactsLists(contacts, sipContacts));
//We need to check sometimes to know if Linphone was destroyed
if (this.isCancelled()) {
return null;
}
if (hasContactsAccess()) {
Cursor c = getPhonesCursor(contentResolver);
if (c != null) {
while (c.moveToNext()) {
String id = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID));
String number = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
LinphoneContact contact = androidContactsCache.get(id);
if (contact != null) {
contact.addNumberOrAddress(new LinphoneNumberOrAddress(number, false));
}
}
c.close();
}
c = getSipCursor(contentResolver);
if (c != null) {
while (c.moveToNext()) {
String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
String sip = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS));
LinphoneContact contact = androidContactsCache.get(id);
if (contact != null) {
contact.addNumberOrAddress(new LinphoneNumberOrAddress(sip, true));
if (!sipContacts.contains(contact)) {
sipContacts.add(contact);
}
}
}
c.close();
}
Collections.sort(sipContacts);
}
publishProgress(new ContactsLists(contacts, sipContacts));
timeElapsed = (new Date()).getTime() - contactsTime.getTime(); timeElapsed = (new Date()).getTime() - contactsTime.getTime();
time = String.format("%02d:%02d", time = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(timeElapsed), TimeUnit.MILLISECONDS.toMinutes(timeElapsed),
TimeUnit.MILLISECONDS.toSeconds(timeElapsed) - TimeUnit.MILLISECONDS.toSeconds(timeElapsed) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeElapsed))); TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeElapsed)));
Log.i("[ContactsManager] organization, SIP and phone numbers for " + contacts.size() + " contacts fetched in " + time); Log.i("[ContactsManager] Step 2 for " + contacts.size() + " contacts executed in " + time);
// Public the current list of contacts without all the informations populated
publishProgress(new ContactsLists(contacts, sipContacts));
for (LinphoneContact contact : contacts) { for (LinphoneContact contact : contacts) {
//We need to check sometimes to know if Linphone was destroyed //We need to check sometimes to know if Linphone was destroyed
if (this.isCancelled()) { if (this.isCancelled()) {
return null; return null;
} }
// This time fetch all informations including phone numbers and SIP addresses // Create the LinphoneFriends matching the native contacts
contact.refresh(); contact.createOrUpdateLinphoneFriendFromNativeContact();
if (contact.isInLinphoneFriendList() && !sipContacts.contains(contact)) {
sipContacts.add(contact);
}
} }
timeElapsed = (new Date()).getTime() - contactsTime.getTime(); timeElapsed = (new Date()).getTime() - contactsTime.getTime();
time = String.format("%02d:%02d", time = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(timeElapsed), TimeUnit.MILLISECONDS.toMinutes(timeElapsed),
TimeUnit.MILLISECONDS.toSeconds(timeElapsed) - TimeUnit.MILLISECONDS.toSeconds(timeElapsed) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeElapsed))); TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeElapsed)));
Log.i("[ContactsManager] linphone friends for " + contacts.size() + " contacts created in " + time); Log.i("[ContactsManager] Step 3 for " + contacts.size() + " contacts executed in " + time);
for (LinphoneFriendList lfl : lc.getFriendLists()) {
Log.d("[ContactsManager] Updating friends subscribtions");
lfl.updateSubscriptions();
}
androidContactsCache.clear();
return new ContactsLists(contacts, sipContacts); return new ContactsLists(contacts, sipContacts);
} }
@ -467,6 +509,8 @@ public class ContactsManager extends ContentObserver {
} }
protected void onPostExecute(ContactsLists result) { protected void onPostExecute(ContactsLists result) {
Log.d("[ContactsManager] Updating contacts subscribtions");
LinphoneManager.getLc().getFriendLists()[0].updateSubscriptions();
for (ContactsUpdatedListener listener : contactsUpdatedListeners) { for (ContactsUpdatedListener listener : contactsUpdatedListeners) {
listener.onContactsUpdated(); listener.onContactsUpdated();
} }
@ -560,4 +604,25 @@ public class ContactsManager extends ContentObserver {
cursor.close(); cursor.close();
return result; return result;
} }
private Cursor getPhonesCursor(ContentResolver cr) {
Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.CONTACT_ID },
null, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " ASC");
return cursor;
}
private Cursor getSipCursor(ContentResolver cr) {
String select = ContactsContract.Data.MIMETYPE + "=?";
String[] projection = new String[] { ContactsContract.Data.CONTACT_ID, ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS };
Cursor c = cr.query(ContactsContract.Data.CONTENT_URI, projection, select, new String[]{ ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE }, null);
return c;
}
private Cursor getOrganizationCursor(ContentResolver cr) {
String select = ContactsContract.Data.MIMETYPE + "=?";
String[] projection = new String[] { ContactsContract.Data.CONTACT_ID, ContactsContract.CommonDataKinds.Organization.COMPANY };
Cursor c = cr.query(ContactsContract.Data.CONTENT_URI, projection, select, new String[]{ ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE }, null);
return c;
}
} }

View file

@ -400,6 +400,10 @@ public class LinphoneContact implements Serializable, Comparable<LinphoneContact
return androidId; return androidId;
} }
public LinphoneFriend getLinphoneFriend() {
return friend;
}
private void createOrUpdateFriend() { private void createOrUpdateFriend() {
boolean created = false; boolean created = false;
LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
@ -495,23 +499,13 @@ public class LinphoneContact implements Serializable, Comparable<LinphoneContact
} }
} }
public void minimalRefresh() { public void refresh() {
minimalRefresh(false);
}
public void minimalRefresh(boolean getAllNames) {
addresses = new ArrayList<LinphoneNumberOrAddress>();
hasSipAddress = false;
if (isAndroidContact()) { if (isAndroidContact()) {
if (getAllNames) {
getContactNames(); getContactNames();
}
boolean isOrgVisible = LinphoneManager.getInstance().getContext().getResources().getBoolean(R.bool.display_contact_organization);
if (isOrgVisible) {
getNativeContactOrganization(); getNativeContactOrganization();
} getAndroidIds();
hasSipAddress = false;
addresses = new ArrayList<LinphoneNumberOrAddress>();
for (LinphoneNumberOrAddress noa : getAddressesAndNumbersForAndroidContact()) { for (LinphoneNumberOrAddress noa : getAddressesAndNumbersForAndroidContact()) {
addNumberOrAddress(noa); addNumberOrAddress(noa);
} }
@ -543,14 +537,8 @@ public class LinphoneContact implements Serializable, Comparable<LinphoneContact
} }
} }
public void refresh() { public void createOrUpdateLinphoneFriendFromNativeContact() {
if (isAndroidContact()) { if (isAndroidContact()) {
androidRawId = findRawContactID();
if (LinphoneManager.getInstance().getContext().getResources().getBoolean(R.bool.use_linphone_tag)) {
androidTagId = findLinphoneRawContactId();
}
createOrUpdateFriend(); createOrUpdateFriend();
} }
} }
@ -585,6 +573,13 @@ public class LinphoneContact implements Serializable, Comparable<LinphoneContact
friend = f; friend = f;
} }
public void getAndroidIds() {
androidRawId = findRawContactID();
if (LinphoneManager.getInstance().getContext().getResources().getBoolean(R.bool.use_linphone_tag)) {
androidTagId = findLinphoneRawContactId();
}
}
public static LinphoneContact createContact() { public static LinphoneContact createContact() {
if (ContactsManager.getInstance().hasContactsAccess()) { if (ContactsManager.getInstance().hasContactsAccess()) {
return createAndroidContact(); return createAndroidContact();
@ -604,7 +599,7 @@ public class LinphoneContact implements Serializable, Comparable<LinphoneContact
private void getContactNames() { private void getContactNames() {
ContentResolver resolver = ContactsManager.getInstance().getContentResolver(); ContentResolver resolver = ContactsManager.getInstance().getContentResolver();
String[] proj = new String[]{ ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, ContactsContract.Contacts.DISPLAY_NAME }; String[] proj = new String[]{ ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME };
String select = ContactsContract.Data.CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?"; String select = ContactsContract.Data.CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] args = new String[]{ getAndroidId(), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE }; String[] args = new String[]{ getAndroidId(), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE };
Cursor c = resolver.query(ContactsContract.Data.CONTENT_URI, proj, select, args, null); Cursor c = resolver.query(ContactsContract.Data.CONTENT_URI, proj, select, args, null);
@ -612,7 +607,6 @@ public class LinphoneContact implements Serializable, Comparable<LinphoneContact
if (c.moveToFirst()) { if (c.moveToFirst()) {
firstName = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)); firstName = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
lastName = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)); lastName = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
fullName = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
} }
c.close(); c.close();
} }

View file

@ -1001,7 +1001,7 @@ public class LinphoneManager implements LinphoneCoreListener, LinphoneChatMessag
public void show(LinphoneCore lc) {} public void show(LinphoneCore lc) {}
public void newSubscriptionRequest(LinphoneCore lc, LinphoneFriend lf, String url) {} public void newSubscriptionRequest(LinphoneCore lc, LinphoneFriend lf, String url) {}
public void notifyPresenceReceived(LinphoneCore lc, LinphoneFriend lf) { public void notifyPresenceReceived(LinphoneCore lc, LinphoneFriend lf) {
ContactsManager.getInstance().refreshSipContacts(); ContactsManager.getInstance().refreshSipContact(lf);
} }
@Override @Override