basic phone number sync
This commit is contained in:
parent
87cc53b8b5
commit
2dee53587b
|
@ -5,7 +5,7 @@ import android.net.Uri;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
abstract class AbstractPhoneContact {
|
public abstract class AbstractPhoneContact {
|
||||||
|
|
||||||
private final Uri lookupUri;
|
private final Uri lookupUri;
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Locale;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
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.JidHelper;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
@ -507,6 +509,33 @@ public class Contact implements ListItem, Blockable {
|
||||||
return serverName;
|
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<?extends AbstractPhoneContact> 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<? extends AbstractPhoneContact> clazz) {
|
||||||
|
if (clazz == PhoneNumberContact.class) {
|
||||||
|
return Options.SYNCED_VIA_ADDRESSBOOK;
|
||||||
|
} else {
|
||||||
|
return Options.SYNCED_VIA_OTHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class Options {
|
public final class Options {
|
||||||
public static final int TO = 0;
|
public static final int TO = 0;
|
||||||
public static final int FROM = 1;
|
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 PENDING_SUBSCRIPTION_REQUEST = 5;
|
||||||
public static final int DIRTY_PUSH = 6;
|
public static final int DIRTY_PUSH = 6;
|
||||||
public static final int DIRTY_DELETE = 7;
|
public static final int DIRTY_DELETE = 7;
|
||||||
|
private static final int SYNCED_VIA_ADDRESSBOOK = 8;
|
||||||
|
private static final int SYNCED_VIA_OTHER = 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.android.AbstractPhoneContact;
|
||||||
import rocks.xmpp.addr.Jid;
|
import rocks.xmpp.addr.Jid;
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,11 +56,12 @@ public class Roster {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Contact> getWithSystemAccounts() {
|
public List<Contact> getWithSystemAccounts(Class<?extends AbstractPhoneContact> clazz) {
|
||||||
|
int option = Contact.getOption(clazz);
|
||||||
List<Contact> with = getContacts();
|
List<Contact> with = getContacts();
|
||||||
for(Iterator<Contact> iterator = with.iterator(); iterator.hasNext();) {
|
for(Iterator<Contact> iterator = with.iterator(); iterator.hasNext();) {
|
||||||
Contact contact = iterator.next();
|
Contact contact = iterator.next();
|
||||||
if (contact.getSystemAccount() == null) {
|
if (!contact.getOption(option)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1523,21 +1523,17 @@ public class XmppConnectionService extends Service {
|
||||||
Map<Jid, JabberIdContact> contacts = JabberIdContact.load(this);
|
Map<Jid, JabberIdContact> contacts = JabberIdContact.load(this);
|
||||||
Log.d(Config.LOGTAG, "start merging phone contacts with roster");
|
Log.d(Config.LOGTAG, "start merging phone contacts with roster");
|
||||||
for (Account account : accounts) {
|
for (Account account : accounts) {
|
||||||
List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts();
|
List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(JabberIdContact.class);
|
||||||
for (JabberIdContact jidContact : contacts.values()) {
|
for (JabberIdContact jidContact : contacts.values()) {
|
||||||
final Contact contact = account.getRoster().getContact(jidContact.getJid());
|
final Contact contact = account.getRoster().getContact(jidContact.getJid());
|
||||||
contact.setSystemAccount(jidContact.getLookupUri());
|
boolean needsCacheClean = contact.setPhoneContact(jidContact);
|
||||||
boolean needsCacheClean = contact.setPhotoUri(jidContact.getPhotoUri());
|
|
||||||
needsCacheClean |= contact.setSystemName(jidContact.getDisplayName());
|
|
||||||
if (needsCacheClean) {
|
if (needsCacheClean) {
|
||||||
getAvatarService().clear(contact);
|
getAvatarService().clear(contact);
|
||||||
}
|
}
|
||||||
withSystemAccounts.remove(contact);
|
withSystemAccounts.remove(contact);
|
||||||
}
|
}
|
||||||
for (Contact contact : withSystemAccounts) {
|
for (Contact contact : withSystemAccounts) {
|
||||||
contact.setSystemAccount(null);
|
boolean needsCacheClean = contact.unsetPhoneContact(JabberIdContact.class);
|
||||||
boolean needsCacheClean = contact.setPhotoUri(null);
|
|
||||||
needsCacheClean |= contact.setSystemName(null);
|
|
||||||
if (needsCacheClean) {
|
if (needsCacheClean) {
|
||||||
getAvatarService().clear(contact);
|
getAvatarService().clear(contact);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.android.JabberIdContact;
|
||||||
import eu.siacs.conversations.android.PhoneNumberContact;
|
import eu.siacs.conversations.android.PhoneNumberContact;
|
||||||
import eu.siacs.conversations.crypto.sasl.Plain;
|
import eu.siacs.conversations.crypto.sasl.Plain;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.utils.AccountUtils;
|
import eu.siacs.conversations.utils.AccountUtils;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
|
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
import io.michaelrocks.libphonenumber.android.Phonenumber;
|
import io.michaelrocks.libphonenumber.android.Phonenumber;
|
||||||
|
@ -63,6 +66,14 @@ public class QuickConversationsService extends AbstractQuickConversationsService
|
||||||
super(xmppConnectionService);
|
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) {
|
public void addOnVerificationRequestedListener(OnVerificationRequested onVerificationRequested) {
|
||||||
synchronized (mOnVerificationRequested) {
|
synchronized (mOnVerificationRequested) {
|
||||||
mOnVerificationRequested.add(onVerificationRequested);
|
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() {
|
public boolean isVerifying() {
|
||||||
return mVerificationInProgress.get();
|
return mVerificationInProgress.get();
|
||||||
}
|
}
|
||||||
|
@ -265,29 +268,94 @@ public class QuickConversationsService extends AbstractQuickConversationsService
|
||||||
@Override
|
@Override
|
||||||
public void considerSync() {
|
public void considerSync() {
|
||||||
Map<String, PhoneNumberContact> contacts = PhoneNumberContact.load(service);
|
Map<String, PhoneNumberContact> contacts = PhoneNumberContact.load(service);
|
||||||
for(Account account : service.getAccounts()) {
|
for (Account account : service.getAccounts()) {
|
||||||
considerSync(account, contacts);
|
considerSync(account, contacts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void considerSync(Account account, Map<String, PhoneNumberContact> contacts) {
|
private void considerSync(Account account, final Map<String, PhoneNumberContact> contacts) {
|
||||||
XmppConnection xmppConnection = account.getXmppConnection();
|
XmppConnection xmppConnection = account.getXmppConnection();
|
||||||
Jid syncServer = xmppConnection == null ? null : xmppConnection.findDiscoItemByFeature(Namespace.SYNCHRONIZATION);
|
Jid syncServer = xmppConnection == null ? null : xmppConnection.findDiscoItemByFeature(Namespace.SYNCHRONIZATION);
|
||||||
if (syncServer == null) {
|
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;
|
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<Element> entries = new ArrayList<>();
|
List<Element> entries = new ArrayList<>();
|
||||||
for(PhoneNumberContact c : contacts.values()) {
|
for (PhoneNumberContact c : contacts.values()) {
|
||||||
entries.add(new Element("entry").setAttribute("number",c.getPhoneNumber()));
|
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<Contact> 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<Jid> jids;
|
||||||
|
private final String number;
|
||||||
|
|
||||||
|
private Entry(String number, List<Jid> jids) {
|
||||||
|
this.number = number;
|
||||||
|
this.jids = jids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Entry of(Element element) {
|
||||||
|
final String number = element.getAttribute("number");
|
||||||
|
final List<Jid> 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<Entry> ofPhoneBook(Element phoneBook) {
|
||||||
|
List<Entry> entries = new ArrayList<>();
|
||||||
|
for (Element entry : phoneBook.getChildren()) {
|
||||||
|
if ("entry".equals(entry.getName())) {
|
||||||
|
entries.add(of(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Jid> 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 {
|
public interface OnVerificationRequested {
|
||||||
|
|
|
@ -53,8 +53,12 @@ public class PhoneNumberUtilWrapper {
|
||||||
return getInstance(context).parse(jid.getEscapedLocal(), "de");
|
return getInstance(context).parse(jid.getEscapedLocal(), "de");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String normalize(Context context, String number) throws NumberParseException {
|
public static String normalize(Context context, String input) throws IllegalArgumentException, NumberParseException {
|
||||||
return normalize(context, getInstance(context).parse(number, getUserCountry(context)));
|
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) {
|
public static String normalize(Context context, Phonenumber.PhoneNumber phoneNumber) {
|
||||||
|
|
Loading…
Reference in a new issue