diff --git a/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java b/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java index 3181f4319..93b7f1a1e 100644 --- a/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java +++ b/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java @@ -5,7 +5,7 @@ import android.net.Uri; import android.provider.ContactsContract; import android.text.TextUtils; -abstract class AbstractPhoneContact { +public abstract class AbstractPhoneContact { private final Uri lookupUri; private final String displayName; diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 9cfa15536..2f0412aff 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -20,6 +20,8 @@ import java.util.Locale; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.android.AbstractPhoneContact; +import eu.siacs.conversations.android.PhoneNumberContact; import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; @@ -507,6 +509,33 @@ public class Contact implements ListItem, Blockable { return serverName; } + public synchronized boolean setPhoneContact(AbstractPhoneContact phoneContact) { + setOption(getOption(phoneContact.getClass())); + setSystemAccount(phoneContact.getLookupUri()); + boolean changed = setSystemName(phoneContact.getDisplayName()); + changed |= setPhotoUri(phoneContact.getPhotoUri()); + return changed; + } + + public synchronized boolean unsetPhoneContact(Class clazz) { + resetOption(getOption(clazz)); + boolean changed = false; + if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) { + setSystemAccount(null); + changed |= setPhotoUri(null); + changed |= setSystemName(null); + } + return changed; + } + + public static int getOption(Class clazz) { + if (clazz == PhoneNumberContact.class) { + return Options.SYNCED_VIA_ADDRESSBOOK; + } else { + return Options.SYNCED_VIA_OTHER; + } + } + public final class Options { public static final int TO = 0; public static final int FROM = 1; @@ -516,5 +545,7 @@ public class Contact implements ListItem, Blockable { public static final int PENDING_SUBSCRIPTION_REQUEST = 5; public static final int DIRTY_PUSH = 6; public static final int DIRTY_DELETE = 7; + private static final int SYNCED_VIA_ADDRESSBOOK = 8; + private static final int SYNCED_VIA_OTHER = 9; } } diff --git a/src/main/java/eu/siacs/conversations/entities/Roster.java b/src/main/java/eu/siacs/conversations/entities/Roster.java index aaff63b3e..f238c96b0 100644 --- a/src/main/java/eu/siacs/conversations/entities/Roster.java +++ b/src/main/java/eu/siacs/conversations/entities/Roster.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import eu.siacs.conversations.android.AbstractPhoneContact; import rocks.xmpp.addr.Jid; @@ -55,11 +56,12 @@ public class Roster { } } - public List getWithSystemAccounts() { + public List getWithSystemAccounts(Class clazz) { + int option = Contact.getOption(clazz); List with = getContacts(); for(Iterator iterator = with.iterator(); iterator.hasNext();) { Contact contact = iterator.next(); - if (contact.getSystemAccount() == null) { + if (!contact.getOption(option)) { iterator.remove(); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 44e948a9c..e6bf39564 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1523,21 +1523,17 @@ public class XmppConnectionService extends Service { Map contacts = JabberIdContact.load(this); Log.d(Config.LOGTAG, "start merging phone contacts with roster"); for (Account account : accounts) { - List withSystemAccounts = account.getRoster().getWithSystemAccounts(); + List withSystemAccounts = account.getRoster().getWithSystemAccounts(JabberIdContact.class); for (JabberIdContact jidContact : contacts.values()) { final Contact contact = account.getRoster().getContact(jidContact.getJid()); - contact.setSystemAccount(jidContact.getLookupUri()); - boolean needsCacheClean = contact.setPhotoUri(jidContact.getPhotoUri()); - needsCacheClean |= contact.setSystemName(jidContact.getDisplayName()); + boolean needsCacheClean = contact.setPhoneContact(jidContact); if (needsCacheClean) { getAvatarService().clear(contact); } withSystemAccounts.remove(contact); } for (Contact contact : withSystemAccounts) { - contact.setSystemAccount(null); - boolean needsCacheClean = contact.setPhotoUri(null); - needsCacheClean |= contact.setSystemName(null); + boolean needsCacheClean = contact.unsetPhoneContact(JabberIdContact.class); if (needsCacheClean) { getAvatarService().clear(contact); } diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index 362bace72..9061a3551 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -27,14 +27,17 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLHandshakeException; import eu.siacs.conversations.Config; +import eu.siacs.conversations.android.JabberIdContact; import eu.siacs.conversations.android.PhoneNumberContact; import eu.siacs.conversations.crypto.sasl.Plain; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import io.michaelrocks.libphonenumber.android.Phonenumber; @@ -63,6 +66,14 @@ public class QuickConversationsService extends AbstractQuickConversationsService super(xmppConnectionService); } + private static long retryAfter(HttpURLConnection connection) { + try { + return SystemClock.elapsedRealtime() + (Long.parseLong(connection.getHeaderField("Retry-After")) * 1000L); + } catch (Exception e) { + return 0; + } + } + public void addOnVerificationRequestedListener(OnVerificationRequested onVerificationRequested) { synchronized (mOnVerificationRequested) { mOnVerificationRequested.add(onVerificationRequested); @@ -246,14 +257,6 @@ public class QuickConversationsService extends AbstractQuickConversationsService } } - private static long retryAfter(HttpURLConnection connection) { - try { - return SystemClock.elapsedRealtime() + (Long.parseLong(connection.getHeaderField("Retry-After")) * 1000L); - } catch (Exception e) { - return 0; - } - } - public boolean isVerifying() { return mVerificationInProgress.get(); } @@ -265,29 +268,94 @@ public class QuickConversationsService extends AbstractQuickConversationsService @Override public void considerSync() { Map contacts = PhoneNumberContact.load(service); - for(Account account : service.getAccounts()) { + for (Account account : service.getAccounts()) { considerSync(account, contacts); } } - private void considerSync(Account account, Map contacts) { + private void considerSync(Account account, final Map contacts) { XmppConnection xmppConnection = account.getXmppConnection(); Jid syncServer = xmppConnection == null ? null : xmppConnection.findDiscoItemByFeature(Namespace.SYNCHRONIZATION); if (syncServer == null) { - Log.d(Config.LOGTAG,account.getJid().asBareJid()+": skipping sync. no sync server found"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping sync. no sync server found"); return; } - Log.d(Config.LOGTAG,account.getJid().asBareJid()+": sending phone list to "+syncServer); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending phone list to " + syncServer); List entries = new ArrayList<>(); - for(PhoneNumberContact c : contacts.values()) { - entries.add(new Element("entry").setAttribute("number",c.getPhoneNumber())); + for (PhoneNumberContact c : contacts.values()) { + entries.add(new Element("entry").setAttribute("number", c.getPhoneNumber())); + } + IqPacket query = new IqPacket(IqPacket.TYPE.GET); + query.setTo(syncServer); + query.addChild(new Element("phone-book", Namespace.SYNCHRONIZATION).setChildren(entries)); + service.sendIqPacket(account, query, (a, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + List withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class); + final Element phoneBook = response.findChild("phone-book", Namespace.SYNCHRONIZATION); + if (phoneBook != null) { + for(Entry entry : Entry.ofPhoneBook(phoneBook)) { + PhoneNumberContact phoneContact = contacts.get(entry.getNumber()); + if (phoneContact == null) { + continue; + } + for(Jid jid : entry.getJids()) { + Contact contact = account.getRoster().getContact(jid); + final boolean needsCacheClean = contact.setPhoneContact(phoneContact); + if (needsCacheClean) { + service.getAvatarService().clear(contact); + } + withSystemAccounts.remove(contact); + } + } + } + for (Contact contact : withSystemAccounts) { + boolean needsCacheClean = contact.unsetPhoneContact(JabberIdContact.class); + if (needsCacheClean) { + service.getAvatarService().clear(contact); + } + } + } + }); + } + + public static class Entry { + private final List jids; + private final String number; + + private Entry(String number, List jids) { + this.number = number; + this.jids = jids; + } + + public static Entry of(Element element) { + final String number = element.getAttribute("number"); + final List jids = new ArrayList<>(); + for (Element jidElement : element.getChildren()) { + String content = jidElement.getContent(); + if (content != null) { + jids.add(Jid.of(content)); + } + } + return new Entry(number, jids); + } + + public static List ofPhoneBook(Element phoneBook) { + List entries = new ArrayList<>(); + for (Element entry : phoneBook.getChildren()) { + if ("entry".equals(entry.getName())) { + entries.add(of(entry)); + } + } + return entries; + } + + public List getJids() { + return jids; + } + + public String getNumber() { + return number; } - Element phoneBook = new Element("phone-book",Namespace.SYNCHRONIZATION); - phoneBook.setChildren(entries); - IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); - iqPacket.setTo(syncServer); - iqPacket.addChild(phoneBook); - service.sendIqPacket(account, iqPacket, null); } public interface OnVerificationRequested { diff --git a/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java index bccad767f..c29dcdf8c 100644 --- a/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java +++ b/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java @@ -53,8 +53,12 @@ public class PhoneNumberUtilWrapper { return getInstance(context).parse(jid.getEscapedLocal(), "de"); } - public static String normalize(Context context, String number) throws NumberParseException { - return normalize(context, getInstance(context).parse(number, getUserCountry(context))); + public static String normalize(Context context, String input) throws IllegalArgumentException, NumberParseException { + final Phonenumber.PhoneNumber number = getInstance(context).parse(input, getUserCountry(context)); + if (!getInstance(context).isValidNumber(number)) { + throw new IllegalArgumentException("Not a valid phone number"); + } + return normalize(context, number); } public static String normalize(Context context, Phonenumber.PhoneNumber phoneNumber) {