package im.actor.sdk.core; import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.SystemClock; import android.provider.ContactsContract; import android.support.v4.content.ContextCompat; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import im.actor.sdk.util.Devices; import im.actor.core.providers.PhoneBookProvider; import im.actor.core.entity.PhoneBookContact; import im.actor.core.entity.PhoneBookEmail; import im.actor.core.entity.PhoneBookPhone; import im.actor.runtime.Log; import im.actor.runtime.android.AndroidContext; public class AndroidPhoneBook implements PhoneBookProvider { private static final int PRELOAD_DELAY = 3000; private static final int READ_ITEM_DELAY_BATCH = 30; private static final int READ_ITEM_DELAY = 10; private static final boolean DISABLE_PHONE_BOOK = false; private static final String TAG = "PhoneBookLoader"; private static final Object initSync = new Object(); private static PhoneNumberUtil PHONE_UTIL; private boolean useDelay = true; @Override public void loadPhoneBook(final Callback callback) { new Thread() { @Override public void run() { if (useDelay) { try { Thread.sleep(PRELOAD_DELAY); } catch (InterruptedException e) { e.printStackTrace(); } } ArrayList<PhoneBookContact> contacts = loadPhoneBook(AndroidContext.getContext(), Devices.getDeviceCountry()); callback.onLoaded(contacts); } }.start(); } public static ArrayList<PhoneBookContact> loadPhoneBook(Context context, String isoCountry) { if (DISABLE_PHONE_BOOK) { return new ArrayList<>(); } Log.d(TAG, "Loading phone book"); long start = SystemClock.uptimeMillis(); HashSet<Long> addedPhones = new HashSet<>(); HashSet<String> addedEmails = new HashSet<>(); ArrayList<PhoneBookContact> records = new ArrayList<>(); HashMap<Long, PhoneBookContact> recordsMap = new HashMap<>(); ContentResolver cr = context.getContentResolver(); if (cr == null) { return new ArrayList<>(); } // Loading records // TODO: Better logic for duplicate phones //Check have permission for this if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { Log.d("Permissions", "contacts - no permission :c"); return new ArrayList<>(); } Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }, null, null, ContactsContract.Contacts.SORT_KEY_PRIMARY ); if (cur == null) { return new ArrayList<>(); } int idIndex = cur.getColumnIndex(ContactsContract.Contacts._ID); int nameIndex = cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); int index = 0; while (cur.moveToNext()) { if (index++ == READ_ITEM_DELAY_BATCH) { if (READ_ITEM_DELAY > 0) { try { Thread.sleep(READ_ITEM_DELAY); } catch (InterruptedException e) { e.printStackTrace(); } } } long id = cur.getLong(idIndex); String name = cur.getString(nameIndex); if (name == null || name.trim().length() == 0) continue; PhoneBookContact record = new PhoneBookContact(id, name.trim(), new ArrayList<>(), new ArrayList<>()); records.add(record); recordsMap.put(id, record); } cur.close(); cur = null; // Loading phones cur = cr.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.NUMBER }, null, null, ContactsContract.CommonDataKinds.Phone._ID + " desc" ); if (cur == null) { return new ArrayList<>(); } final int idContactIndex = cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID); final int idPhoneIndex = cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID); final int idNumberIndex = cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); while (cur.moveToNext()) { if (index++ == READ_ITEM_DELAY_BATCH) { if (READ_ITEM_DELAY > 0) { try { Thread.sleep(READ_ITEM_DELAY); } catch (InterruptedException e) { e.printStackTrace(); } } } long contactId = cur.getLong(idContactIndex); long phoneId = cur.getLong(idPhoneIndex); String rawPhone = cur.getString(idNumberIndex); PhoneBookContact record = recordsMap.get(contactId); if (record == null || rawPhone == null) { continue; } if (PHONE_UTIL == null) { synchronized (initSync) { if (PHONE_UTIL == null) { PHONE_UTIL = PhoneNumberUtil.getInstance(); } } } try { final Phonenumber.PhoneNumber phonenumber = PHONE_UTIL.parse(rawPhone, isoCountry); rawPhone = phonenumber.getCountryCode() + "" + phonenumber.getNationalNumber(); } catch (final NumberParseException e) { rawPhone = rawPhone.replaceAll("[^\\d]", ""); } long phone = -1; try { phone = Long.parseLong(rawPhone); } catch (Exception e) { // Logger.d(TAG, "Can't parse number", e); continue; } if (addedPhones.contains(phone)) { continue; } addedPhones.add(phone); record.getPhones().add(new PhoneBookPhone(phoneId, phone)); } cur.close(); cur = null; // Loading emails cur = cr.query( ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Email._ID, ContactsContract.CommonDataKinds.Email.CONTACT_ID, ContactsContract.CommonDataKinds.Email.ADDRESS }, null, null, ContactsContract.CommonDataKinds.Email._ID + " desc" ); if (cur == null) { return new ArrayList<PhoneBookContact>(); } final int idEmailContactIndex = cur.getColumnIndex(ContactsContract.CommonDataKinds.Email.CONTACT_ID); final int idEmailIdIndex = cur.getColumnIndex(ContactsContract.CommonDataKinds.Email._ID); final int idEmailIndex = cur.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS); while (cur.moveToNext()) { if (index++ == READ_ITEM_DELAY_BATCH) { if (READ_ITEM_DELAY > 0) { try { Thread.sleep(READ_ITEM_DELAY); } catch (InterruptedException e) { e.printStackTrace(); } } } long contactId = cur.getLong(idEmailContactIndex); long emailId = cur.getLong(idEmailIdIndex); String rawEmail = cur.getString(idEmailIndex); PhoneBookContact record = recordsMap.get(contactId); if (record == null) { continue; } if (rawEmail == null) { continue; } rawEmail = rawEmail.toLowerCase(); if (addedEmails.contains(rawEmail)) { continue; } addedEmails.add(rawEmail); record.getEmails().add(new PhoneBookEmail(emailId, rawEmail)); } cur.close(); // Filtering records without contacts ArrayList<PhoneBookContact> res = new ArrayList<>(); for (PhoneBookContact rec : records) { if (rec.getPhones().size() > 0 || rec.getEmails().size() > 0) { res.add(rec); } } Log.d(TAG, "Phone book loaded in " + (SystemClock.uptimeMillis() - start) + " ms in " + (index) + " iterations"); return res; } public void useDelay(boolean useDelay) { this.useDelay = useDelay; } }