/* * 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.contacts; import android.annotation.SuppressLint; import android.content.Context; import android.database.Cursor; import android.os.AsyncTask; import android.provider.ContactsContract; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.compatibility.Compatibility; import org.linphone.core.Core; import org.linphone.core.Friend; import org.linphone.core.FriendList; import org.linphone.core.tools.Log; import org.linphone.settings.LinphonePreferences; import org.linphone.utils.LinphoneUtils; class AsyncContactsLoader extends AsyncTask { @SuppressLint("InlinedApi") public static final String[] PROJECTION = { ContactsContract.Data.CONTACT_ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, ContactsContract.Data.MIMETYPE, ContactsContract.Contacts.STARRED, "data1", // Company, Phone or SIP Address "data2", // ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME "data3", // ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME "data4", // Normalized phone number }; private Context mContext; public AsyncContactsLoader(Context context) { mContext = context; } @Override protected void onPreExecute() { Log.i("[Contacts Manager] Synchronization started"); if (LinphonePreferences.instance().isFriendlistsubscriptionEnabled()) { String rls = mContext.getString(R.string.rls_uri); for (FriendList list : LinphoneManager.getCore().getFriendsLists()) { if (list.getRlsAddress() == null || !list.getRlsAddress().asStringUriOnly().equals(rls)) { list.setRlsUri(rls); } list.addListener(ContactsManager.getInstance()); } } } @Override protected AsyncContactsData doInBackground(Void... params) { Log.i("[Contacts Manager] Background synchronization started"); HashMap androidContactsCache = new HashMap<>(); AsyncContactsData data = new AsyncContactsData(); List nativeIds = new ArrayList<>(); Core core = LinphoneManager.getCore(); if (core != null) { FriendList[] friendLists = core.getFriendsLists(); for (FriendList list : friendLists) { Friend[] friends = list.getFriends(); for (Friend friend : friends) { if (isCancelled()) { Log.w("[Contacts Manager] Task cancelled"); return data; } LinphoneContact contact = (LinphoneContact) friend.getUserData(); if (contact != null) { if (contact.getAndroidId() != null) { contact.clearAddresses(); androidContactsCache.put(contact.getAndroidId(), contact); nativeIds.add(contact.getAndroidId()); } else { data.contacts.add(contact); } } else { if (friend.getRefKey() != null) { // Friend has a refkey but no LinphoneContact => represents a // native contact stored in db from a previous version of Linphone, // remove it list.removeFriend(friend); } else { // No refkey so it's a standalone contact contact = new LinphoneContact(); contact.setFriend(friend); contact.syncValuesFromFriend(); data.contacts.add(contact); } } } } } if (ContactsManager.getInstance().hasReadContactsAccess()) { String selection = null; if (mContext.getResources().getBoolean(R.bool.fetch_contacts_from_default_directory)) { Log.i("[Contacts Manager] Only fetching contacts in default directory"); selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1"; } Cursor c = mContext.getContentResolver() .query( ContactsContract.Data.CONTENT_URI, PROJECTION, selection, null, null); if (c != null) { Log.i("[Contacts Manager] Found " + c.getCount() + " entries in cursor"); while (c.moveToNext()) { if (isCancelled()) { Log.w("[Contacts Manager] Task cancelled"); return data; } try { String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID)); boolean starred = c.getInt(c.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1; LinphoneContact contact = androidContactsCache.get(id); if (contact == null) { Log.d( "[Contacts Manager] Creating LinphoneContact with native ID " + id + ", favorite flag is " + starred); nativeIds.add(id); contact = new LinphoneContact(); contact.setAndroidId(id); contact.setIsFavourite(starred); androidContactsCache.put(id, contact); } contact.syncValuesFromAndroidCusor(c); } catch (IllegalStateException ise) { Log.e( "[Contacts Manager] Couldn't get values from cursor, exception: ", ise); } } c.close(); } FriendList[] friendLists = core.getFriendsLists(); for (FriendList list : friendLists) { Friend[] friends = list.getFriends(); for (Friend friend : friends) { if (isCancelled()) { Log.w("[Contacts Manager] Task cancelled"); return data; } LinphoneContact contact = (LinphoneContact) friend.getUserData(); if (contact != null && contact.isAndroidContact()) { String id = contact.getAndroidId(); if (id != null && !nativeIds.contains(id)) { Log.i("[Contacts Manager] Contact removed since last fetch: " + id); // Has been removed since last fetch androidContactsCache.remove(id); } } } } nativeIds.clear(); } Collection contacts = androidContactsCache.values(); // New friends count will be 0 after the first contacts fetch Log.i( "[Contacts Manager] Found " + contacts.size() + " native contacts plus " + data.contacts.size() + " friends in the configuration file"); for (LinphoneContact contact : contacts) { if (isCancelled()) { Log.w("[Contacts Manager] Task cancelled"); return data; } if (contact.getNumbersOrAddresses().isEmpty()) { continue; } if (contact.getFullName() == null) { for (LinphoneNumberOrAddress noa : contact.getNumbersOrAddresses()) { if (noa.isSIPAddress()) { contact.setFullName(LinphoneUtils.getAddressDisplayName(noa.getValue())); Log.w( "[Contacts Manager] Couldn't find a display name for contact " + contact.getFullName() + ", used SIP address display name / username instead..."); break; } } } /*if (contact.getFriend() != null) { for (LinphoneNumberOrAddress noa : contact.getNumbersOrAddresses()) { PresenceModel pm = contact.getFriend().getPresenceModelForUriOrTel(noa.getValue()); if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) { data.sipContacts.add(contact); break; } } }*/ if (!mContext.getResources().getBoolean(R.bool.hide_sip_contacts_without_presence)) { if (contact.hasAddress() && !data.sipContacts.contains(contact)) { data.sipContacts.add(contact); } } data.contacts.add(contact); } androidContactsCache.clear(); Collections.sort(data.contacts); Collections.sort(data.sipContacts); Log.i("[Contacts Manager] Background synchronization finished"); return data; } @Override protected void onPostExecute(AsyncContactsData data) { Log.i( "[Contacts Manager] " + data.contacts.size() + " contacts found in which " + data.sipContacts.size() + " are SIP"); for (LinphoneContact contact : data.contacts) { contact.createOrUpdateFriendFromNativeContact(); } // Now that contact fetching is asynchronous, this is required to ensure // presence subscription event will be sent with all friends if (LinphonePreferences.instance().isFriendlistsubscriptionEnabled()) { Log.i("[Contacts Manager] Matching friends created, updating subscription"); FriendList[] friendLists = LinphoneManager.getCore().getFriendsLists(); for (FriendList list : friendLists) { list.updateSubscriptions(); } } ContactsManager.getInstance().setContacts(data.contacts); ContactsManager.getInstance().setSipContacts(data.sipContacts); for (ContactsUpdatedListener listener : ContactsManager.getInstance().getContactsListeners()) { listener.onContactsUpdated(); } Compatibility.createChatShortcuts(mContext); Log.i("[Contacts Manager] Synchronization finished"); } class AsyncContactsData { final List contacts; final List sipContacts; AsyncContactsData() { contacts = new ArrayList<>(); sipContacts = new ArrayList<>(); } } }