From a49a5790c79c1a9d59e1934159d0d67c326d1322 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 27 Oct 2018 00:32:09 +0200 Subject: [PATCH] refactored phone contact loading in preperation for sync --- .../services/QuickConversationsService.java | 4 + .../android/AbstractPhoneContact.java | 39 ++++++++ .../android/JabberIdContact.java | 74 +++++++++++++++ .../android/OnPhoneContactsLoaded.java | 8 ++ .../siacs/conversations/entities/Contact.java | 32 +++---- .../services/ShortcutService.java | 2 +- .../services/XmppConnectionService.java | 73 +++++++-------- .../conversations/utils/PhoneHelper.java | 67 ------------- .../ReplacingSerialSingleThreadExecutor.java | 10 +- .../utils/ReplacingTaskManager.java | 2 +- .../utils/SerialSingleThreadExecutor.java | 93 +++++++++---------- .../android/PhoneNumberContact.java | 69 ++++++++++++++ .../services/QuickConversationsService.java | 9 ++ 13 files changed, 296 insertions(+), 186 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java create mode 100644 src/main/java/eu/siacs/conversations/android/JabberIdContact.java create mode 100644 src/main/java/eu/siacs/conversations/android/OnPhoneContactsLoaded.java create mode 100644 src/quick/java/eu/siacs/conversations/android/PhoneNumberContact.java diff --git a/src/full/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/full/java/eu/siacs/conversations/services/QuickConversationsService.java index 9ba02a556..233499dc6 100644 --- a/src/full/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/full/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -15,4 +15,8 @@ public class QuickConversationsService { public static boolean isFull() { return true; } + + public void considerSync() { + + } } \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java b/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java new file mode 100644 index 000000000..3181f4319 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java @@ -0,0 +1,39 @@ +package eu.siacs.conversations.android; + +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.text.TextUtils; + +abstract class AbstractPhoneContact { + + private final Uri lookupUri; + private final String displayName; + private final String photoUri; + + + AbstractPhoneContact(Cursor cursor) { + int phoneId = cursor.getInt(cursor.getColumnIndex(ContactsContract.Data._ID)); + String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)); + this.lookupUri = ContactsContract.Contacts.getLookupUri(phoneId, lookupKey); + this.displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)); + this.photoUri = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.PHOTO_URI)); + } + + public Uri getLookupUri() { + return lookupUri; + } + + public String getDisplayName() { + return displayName; + } + + public String getPhotoUri() { + return photoUri; + } + + + public int rating() { + return (TextUtils.isEmpty(displayName) ? 0 : 2) + (TextUtils.isEmpty(photoUri) ? 0 : 1); + } +} diff --git a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java new file mode 100644 index 000000000..8d7aa90f4 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java @@ -0,0 +1,74 @@ +package eu.siacs.conversations.android; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.Build; +import android.provider.ContactsContract; +import android.util.Log; + +import java.util.Collections; +import java.util.HashMap; + +import eu.siacs.conversations.Config; +import rocks.xmpp.addr.Jid; + +public class JabberIdContact extends AbstractPhoneContact { + + private final Jid jid; + + private JabberIdContact(Cursor cursor) throws IllegalArgumentException { + super(cursor); + try { + this.jid = Jid.of(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + } catch (IllegalArgumentException | NullPointerException e) { + throw new IllegalArgumentException(e); + } + } + + public Jid getJid() { + return jid; + } + + public static void load(Context context, OnPhoneContactsLoaded callback) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + callback.onPhoneContactsLoaded(Collections.emptyList()); + return; + } + final String[] PROJECTION = new String[]{ContactsContract.Data._ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.PHOTO_URI, + ContactsContract.Data.LOOKUP_KEY, + ContactsContract.CommonDataKinds.Im.DATA}; + + final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" + + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE + + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + + "\")"; + final Cursor cursor; + try { + cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null); + } catch (Exception e) { + callback.onPhoneContactsLoaded(Collections.emptyList()); + return; + } + final HashMap contacts = new HashMap<>(); + while (cursor != null && cursor.moveToNext()) { + try { + final JabberIdContact contact = new JabberIdContact(cursor); + final JabberIdContact preexisting = contacts.put(contact.getJid(), contact); + if (preexisting == null || preexisting.rating() < contact.rating()) { + contacts.put(contact.getJid(), contact); + } + } catch (IllegalArgumentException e) { + Log.d(Config.LOGTAG,"unable to create jabber id contact"); + } + } + if (cursor != null) { + cursor.close(); + } + callback.onPhoneContactsLoaded(contacts.values()); + } +} diff --git a/src/main/java/eu/siacs/conversations/android/OnPhoneContactsLoaded.java b/src/main/java/eu/siacs/conversations/android/OnPhoneContactsLoaded.java new file mode 100644 index 000000000..f2e8752c3 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/android/OnPhoneContactsLoaded.java @@ -0,0 +1,8 @@ +package eu.siacs.conversations.android; + +import java.util.Collection; + +public interface OnPhoneContactsLoaded { + + void onPhoneContactsLoaded(Collection contacts); +} diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index f8bc21c7d..9cfa15536 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -48,7 +48,7 @@ public class Contact implements ListItem, Blockable { private String commonName; protected Jid jid; private int subscription = 0; - private String systemAccount; + private Uri systemAccount; private String photoUri; private final JSONObject keys; private JSONArray groups = new JSONArray(); @@ -62,7 +62,7 @@ public class Contact implements ListItem, Blockable { public Contact(final String account, final String systemName, final String serverName, final Jid jid, final int subscription, final String photoUri, - final String systemAccount, final String keys, final String avatar, final long lastseen, + final Uri systemAccount, final String keys, final String avatar, final long lastseen, final String presence, final String groups) { this.accountUuid = account; this.systemName = systemName; @@ -105,13 +105,19 @@ public class Contact implements ListItem, Blockable { // TODO: Borked DB... handle this somehow? return null; } + Uri systemAccount; + try { + systemAccount = Uri.parse(cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT))); + } catch (Exception e) { + systemAccount = null; + } return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), cursor.getString(cursor.getColumnIndex(SERVERNAME)), jid, cursor.getInt(cursor.getColumnIndex(OPTIONS)), cursor.getString(cursor.getColumnIndex(PHOTOURI)), - cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)), + systemAccount, cursor.getString(cursor.getColumnIndex(KEYS)), cursor.getString(cursor.getColumnIndex(AVATAR)), cursor.getLong(cursor.getColumnIndex(LAST_TIME)), @@ -200,7 +206,7 @@ public class Contact implements ListItem, Blockable { values.put(SERVERNAME, serverName); values.put(JID, jid.toString()); values.put(OPTIONS, subscription); - values.put(SYSTEMACCOUNT, systemAccount); + values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null); values.put(PHOTOURI, photoUri); values.put(KEYS, keys.toString()); values.put(AVATAR, avatar == null ? null : avatar.getFilename()); @@ -270,21 +276,11 @@ public class Contact implements ListItem, Blockable { } public Uri getSystemAccount() { - if (systemAccount == null) { - return null; - } else { - String[] parts = systemAccount.split("#"); - if (parts.length != 2) { - return null; - } else { - long id = Long.parseLong(parts[0]); - return ContactsContract.Contacts.getLookupUri(id, parts[1]); - } - } + return systemAccount; } - public void setSystemAccount(String account) { - this.systemAccount = account; + public void setSystemAccount(Uri lookupUri) { + this.systemAccount = lookupUri; } private Collection getGroups(final boolean unique) { @@ -343,7 +339,7 @@ public class Contact implements ListItem, Blockable { } public boolean showInPhoneBook() { - return systemAccount != null && !systemAccount.trim().isEmpty(); + return systemAccount != null; } public void parseSubscriptionFromElement(Element item) { diff --git a/src/main/java/eu/siacs/conversations/services/ShortcutService.java b/src/main/java/eu/siacs/conversations/services/ShortcutService.java index dc4f1ce28..6db4d92fd 100644 --- a/src/main/java/eu/siacs/conversations/services/ShortcutService.java +++ b/src/main/java/eu/siacs/conversations/services/ShortcutService.java @@ -25,7 +25,7 @@ import rocks.xmpp.addr.Jid; public class ShortcutService { private final XmppConnectionService xmppConnectionService; - private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(false); + private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName()); public ShortcutService(XmppConnectionService xmppConnectionService) { this.xmppConnectionService = xmppConnectionService; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 16cd2fa0f..1aa6004c6 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -71,6 +71,7 @@ import java.util.concurrent.atomic.AtomicLong; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.android.JabberIdContact; import eu.siacs.conversations.crypto.OmemoSetting; import eu.siacs.conversations.crypto.PgpDecryptionService; import eu.siacs.conversations.crypto.PgpEngine; @@ -115,7 +116,6 @@ import eu.siacs.conversations.utils.ConversationsFileObserver; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.MimeUtils; -import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.QuickLoader; import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor; @@ -192,7 +192,7 @@ public class XmppConnectionService extends Service { } }; public DatabaseBackend databaseBackend; - private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true); + private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); private long mLastActivity = 0; private FileBackend fileBackend = new FileBackend(this); private MemorizingTrustManager mMemorizingTrustManager; @@ -1519,45 +1519,36 @@ public class XmppConnectionService extends Service { } public void loadPhoneContacts() { - mContactMergerExecutor.execute(() -> PhoneHelper.loadPhoneContacts(XmppConnectionService.this, new OnPhoneContactsLoadedListener() { - @Override - public void onPhoneContactsLoaded(List phoneContacts) { - Log.d(Config.LOGTAG, "start merging phone contacts with roster"); - for (Account account : accounts) { - List withSystemAccounts = account.getRoster().getWithSystemAccounts(); - for (Bundle phoneContact : phoneContacts) { - Jid jid; - try { - jid = Jid.of(phoneContact.getString("jid")); - } catch (final IllegalArgumentException e) { - continue; - } - final Contact contact = account.getRoster().getContact(jid); - String systemAccount = phoneContact.getInt("phoneid") - + "#" - + phoneContact.getString("lookup"); - contact.setSystemAccount(systemAccount); - boolean needsCacheClean = contact.setPhotoUri(phoneContact.getString("photouri")); - needsCacheClean |= contact.setSystemName(phoneContact.getString("displayname")); - if (needsCacheClean) { - getAvatarService().clear(contact); - } - withSystemAccounts.remove(contact); - } - for (Contact contact : withSystemAccounts) { - contact.setSystemAccount(null); - boolean needsCacheClean = contact.setPhotoUri(null); - needsCacheClean |= contact.setSystemName(null); - if (needsCacheClean) { - getAvatarService().clear(contact); - } - } - } - Log.d(Config.LOGTAG, "finished merging phone contacts"); - mShortcutService.refresh(mInitialAddressbookSyncCompleted.compareAndSet(false, true)); - updateRosterUi(); - } - })); + mContactMergerExecutor.execute(() -> { + JabberIdContact.load(this, contacts -> { + Log.d(Config.LOGTAG, "start merging phone contacts with roster"); + for (Account account : accounts) { + List withSystemAccounts = account.getRoster().getWithSystemAccounts(); + for (JabberIdContact jidContact : contacts) { + final Contact contact = account.getRoster().getContact(jidContact.getJid()); + contact.setSystemAccount(jidContact.getLookupUri()); + boolean needsCacheClean = contact.setPhotoUri(jidContact.getPhotoUri()); + needsCacheClean |= contact.setSystemName(jidContact.getDisplayName()); + if (needsCacheClean) { + getAvatarService().clear(contact); + } + withSystemAccounts.remove(contact); + } + for (Contact contact : withSystemAccounts) { + contact.setSystemAccount(null); + boolean needsCacheClean = contact.setPhotoUri(null); + needsCacheClean |= contact.setSystemName(null); + if (needsCacheClean) { + getAvatarService().clear(contact); + } + } + } + Log.d(Config.LOGTAG, "finished merging phone contacts"); + mShortcutService.refresh(mInitialAddressbookSyncCompleted.compareAndSet(false, true)); + updateRosterUi(); + }); + mQuickConversationsService.considerSync(); + }); } diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java index 65aba4c39..e849730b7 100644 --- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java @@ -24,55 +24,6 @@ public class PhoneHelper { return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); } - public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) { - final List phoneContacts = new ArrayList<>(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { - listener.onPhoneContactsLoaded(phoneContacts); - return; - } - final String[] PROJECTION = new String[]{ContactsContract.Data._ID, - ContactsContract.Data.DISPLAY_NAME, - ContactsContract.Data.PHOTO_URI, - ContactsContract.Data.LOOKUP_KEY, - ContactsContract.CommonDataKinds.Im.DATA}; - - final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" - + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE - + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL - + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER - + "\")"; - - CursorLoader mCursorLoader = new NotThrowCursorLoader(context, - ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, - null); - mCursorLoader.registerListener(0, (arg0, c) -> { - if (c != null) { - while (c.moveToNext()) { - Bundle contact = new Bundle(); - contact.putInt("phoneid", c.getInt(c.getColumnIndex(ContactsContract.Data._ID))); - contact.putString("displayname", c.getString(c.getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); - contact.putString("photouri", c.getString(c.getColumnIndex(ContactsContract.Data.PHOTO_URI))); - contact.putString("lookup", c.getString(c.getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); - contact.putString("jid", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); - phoneContacts.add(contact); - } - c.close(); - } - - if (listener != null) { - listener.onPhoneContactsLoaded(phoneContacts); - } - }); - try { - mCursorLoader.startLoading(); - } catch (RejectedExecutionException e) { - if (listener != null) { - listener.onPhoneContactsLoaded(phoneContacts); - } - } - } - public static Uri getProfilePictureUri(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { return null; @@ -104,22 +55,4 @@ public class PhoneHelper { return "unknown"; } } - - private static class NotThrowCursorLoader extends CursorLoader { - - private NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) { - super(c, u, p, s, sa, so); - } - - @Override - public Cursor loadInBackground() { - - try { - return (super.loadInBackground()); - } catch (Throwable e) { - return (null); - } - } - - } } diff --git a/src/main/java/eu/siacs/conversations/utils/ReplacingSerialSingleThreadExecutor.java b/src/main/java/eu/siacs/conversations/utils/ReplacingSerialSingleThreadExecutor.java index c42c1ab1a..df9e7927f 100644 --- a/src/main/java/eu/siacs/conversations/utils/ReplacingSerialSingleThreadExecutor.java +++ b/src/main/java/eu/siacs/conversations/utils/ReplacingSerialSingleThreadExecutor.java @@ -3,17 +3,13 @@ package eu.siacs.conversations.utils; public class ReplacingSerialSingleThreadExecutor extends SerialSingleThreadExecutor { public ReplacingSerialSingleThreadExecutor(String name) { - super(name, false); - } - - public ReplacingSerialSingleThreadExecutor(boolean prepareLooper) { - super(ReplacingSerialSingleThreadExecutor.class.getName(), prepareLooper); + super(name); } @Override public synchronized void execute(final Runnable r) { tasks.clear(); - if (active != null && active instanceof Cancellable) { + if (active instanceof Cancellable) { ((Cancellable) active).cancel(); } super.execute(r); @@ -21,7 +17,7 @@ public class ReplacingSerialSingleThreadExecutor extends SerialSingleThreadExecu public synchronized void cancelRunningTasks() { tasks.clear(); - if (active != null && active instanceof Cancellable) { + if (active instanceof Cancellable) { ((Cancellable) active).cancel(); } } diff --git a/src/main/java/eu/siacs/conversations/utils/ReplacingTaskManager.java b/src/main/java/eu/siacs/conversations/utils/ReplacingTaskManager.java index a06988373..396bfa3ec 100644 --- a/src/main/java/eu/siacs/conversations/utils/ReplacingTaskManager.java +++ b/src/main/java/eu/siacs/conversations/utils/ReplacingTaskManager.java @@ -42,7 +42,7 @@ public class ReplacingTaskManager { synchronized (this.executors) { executor = this.executors.get(account); if (executor == null) { - executor = new ReplacingSerialSingleThreadExecutor(false); + executor = new ReplacingSerialSingleThreadExecutor(ReplacingTaskManager.class.getSimpleName()); this.executors.put(account, executor); } executor.execute(runnable); diff --git a/src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java b/src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java index 0afe11430..ce54f4dd3 100644 --- a/src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java +++ b/src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java @@ -1,73 +1,64 @@ package eu.siacs.conversations.utils; -import android.os.Looper; import android.util.Log; import java.util.ArrayDeque; -import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import eu.siacs.conversations.Config; -import eu.siacs.conversations.services.AttachFileToConversationRunnable; public class SerialSingleThreadExecutor implements Executor { - private final Executor executor = Executors.newSingleThreadExecutor(); - final ArrayDeque tasks = new ArrayDeque<>(); - protected Runnable active; - private final String name; + final ArrayDeque tasks = new ArrayDeque<>(); + private final Executor executor = Executors.newSingleThreadExecutor(); + private final String name; + protected Runnable active; - public SerialSingleThreadExecutor(String name) { - this(name, false); - } - SerialSingleThreadExecutor(String name, boolean prepareLooper) { - if (prepareLooper) { - execute(Looper::prepare); - } - this.name = name; - } + public SerialSingleThreadExecutor(String name) { + this.name = name; + } - public synchronized void execute(final Runnable r) { - tasks.offer(new Runner(r)); - if (active == null) { - scheduleNext(); - } - } + public synchronized void execute(final Runnable r) { + tasks.offer(new Runner(r)); + if (active == null) { + scheduleNext(); + } + } - private synchronized void scheduleNext() { - if ((active = tasks.poll()) != null) { - executor.execute(active); - int remaining = tasks.size(); - if (remaining > 0) { - Log.d(Config.LOGTAG,remaining+" remaining tasks on executor '"+name+"'"); - } - } - } + private synchronized void scheduleNext() { + if ((active = tasks.poll()) != null) { + executor.execute(active); + int remaining = tasks.size(); + if (remaining > 0) { + Log.d(Config.LOGTAG, remaining + " remaining tasks on executor '" + name + "'"); + } + } + } - private class Runner implements Runnable, Cancellable { + private class Runner implements Runnable, Cancellable { - private final Runnable runnable; + private final Runnable runnable; - private Runner(Runnable runnable) { - this.runnable = runnable; - } + private Runner(Runnable runnable) { + this.runnable = runnable; + } - @Override - public void cancel() { - if (runnable instanceof Cancellable) { - ((Cancellable) runnable).cancel(); - } - } + @Override + public void cancel() { + if (runnable instanceof Cancellable) { + ((Cancellable) runnable).cancel(); + } + } - @Override - public void run() { - try { - runnable.run(); - } finally { - scheduleNext(); - } - } - } + @Override + public void run() { + try { + runnable.run(); + } finally { + scheduleNext(); + } + } + } } \ No newline at end of file diff --git a/src/quick/java/eu/siacs/conversations/android/PhoneNumberContact.java b/src/quick/java/eu/siacs/conversations/android/PhoneNumberContact.java new file mode 100644 index 000000000..196063e9a --- /dev/null +++ b/src/quick/java/eu/siacs/conversations/android/PhoneNumberContact.java @@ -0,0 +1,69 @@ +package eu.siacs.conversations.android; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.Build; +import android.provider.ContactsContract; +import android.util.Log; + +import java.util.Collections; +import java.util.HashMap; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; +import io.michaelrocks.libphonenumber.android.NumberParseException; + +public class PhoneNumberContact extends AbstractPhoneContact { + + private String phoneNumber; + + public String getPhoneNumber() { + return phoneNumber; + } + + private PhoneNumberContact(Context context, Cursor cursor) throws IllegalArgumentException { + super(cursor); + try { + this.phoneNumber = PhoneNumberUtilWrapper.normalize(context,cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))); + } catch (NumberParseException | NullPointerException e) { + throw new IllegalArgumentException(e); + } + } + + public static void load(Context context, OnPhoneContactsLoaded callback) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + callback.onPhoneContactsLoaded(Collections.emptyList()); + return; + } + final String[] PROJECTION = new String[]{ContactsContract.Data._ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.PHOTO_URI, + ContactsContract.Data.LOOKUP_KEY, + ContactsContract.CommonDataKinds.Phone.NUMBER}; + final Cursor cursor; + try { + cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null); + } catch (Exception e) { + callback.onPhoneContactsLoaded(Collections.emptyList()); + return; + } + final HashMap contacts = new HashMap<>(); + while (cursor != null && cursor.moveToNext()) { + try { + final PhoneNumberContact contact = new PhoneNumberContact(context, cursor); + final PhoneNumberContact preexisting = contacts.get(contact.getPhoneNumber()); + if (preexisting == null || preexisting.rating() < contact.rating()) { + contacts.put(contact.getPhoneNumber(), contact); + } + } catch (IllegalArgumentException e) { + Log.d(Config.LOGTAG, "unable to create phone contact"); + } + } + if (cursor != null) { + cursor.close(); + } + callback.onPhoneContactsLoaded(contacts.values()); + } +} diff --git a/src/quick/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quick/java/eu/siacs/conversations/services/QuickConversationsService.java index 5cfb048a8..5709b8355 100644 --- a/src/quick/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quick/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLHandshakeException; import eu.siacs.conversations.Config; +import eu.siacs.conversations.android.PhoneNumberContact; import eu.siacs.conversations.crypto.sasl.Plain; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.AccountUtils; @@ -264,6 +265,14 @@ public class QuickConversationsService { return false; } + public void considerSync() { + PhoneNumberContact.load(service, contacts -> { + for(PhoneNumberContact c : contacts) { + Log.d(Config.LOGTAG, "Display Name=" + c.getDisplayName() + ", number=" + c.getPhoneNumber()+", uri="+c.getLookupUri()); + } + }); + } + public interface OnVerificationRequested { void onVerificationRequestFailed(int code);