QuickConversationsService.java

  1package eu.siacs.conversations.services;
  2
  3import java.util.concurrent.atomic.AtomicInteger;
  4import java.util.Collection;
  5import java.util.Collections;
  6import java.util.HashMap;
  7import java.util.Set;
  8import java.util.Objects;
  9import java.util.ArrayList;
 10import java.util.List;
 11import java.util.Map;
 12import java.util.stream.Collectors;
 13import java.util.stream.Stream;
 14
 15import com.google.common.collect.ImmutableMap;
 16import com.google.common.collect.ImmutableList;
 17
 18import android.content.Intent;
 19import android.os.SystemClock;
 20import android.net.Uri;
 21import android.util.Log;
 22
 23import eu.siacs.conversations.Config;
 24import eu.siacs.conversations.android.PhoneNumberContact;
 25import eu.siacs.conversations.entities.Account;
 26import eu.siacs.conversations.entities.Contact;
 27import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
 28import eu.siacs.conversations.xmpp.Jid;
 29import eu.siacs.conversations.xmpp.manager.RosterManager;
 30
 31public class QuickConversationsService extends AbstractQuickConversationsService {
 32
 33    protected final AtomicInteger mRunningSyncJobs = new AtomicInteger(0);
 34    protected final SerialSingleThreadExecutor mSerialSingleThreadExecutor = new SerialSingleThreadExecutor(QuickConversationsService.class.getSimpleName());
 35    protected HashMap<String,Attempt> mLastSyncAttempt = new HashMap<>();
 36
 37    QuickConversationsService(XmppConnectionService xmppConnectionService) {
 38        super(xmppConnectionService);
 39    }
 40
 41    @Override
 42    public void considerSync() {
 43        considerSync(false);
 44    }
 45
 46    @Override
 47    public void signalAccountStateChange() {
 48
 49    }
 50
 51    @Override
 52    public boolean isSynchronizing() {
 53        return mRunningSyncJobs.get() > 0;
 54    }
 55
 56    @Override
 57    public void considerSyncBackground(boolean force) {
 58        mRunningSyncJobs.incrementAndGet();
 59        mSerialSingleThreadExecutor.execute(() -> {
 60            considerSync(force);
 61            if (mRunningSyncJobs.decrementAndGet() == 0) {
 62                service.updateRosterUi(XmppConnectionService.UpdateRosterReason.INIT);
 63            }
 64        });
 65    }
 66
 67    @Override
 68    public void handleSmsReceived(Intent intent) {
 69        Log.d(Config.LOGTAG,"ignoring received SMS");
 70    }
 71
 72    protected static String getNumber(final Set<String> gateways, final Contact contact) {
 73        final Jid jid = contact.getJid();
 74        if (jid.getLocal() != null && ("quicksy.im".equals(jid.getDomain()) || gateways.contains(jid.getDomain()))) {
 75            return jid.getLocal();
 76        }
 77        return null;
 78    }
 79
 80    protected void refresh(Account account, final Set<String> gateways, Collection<PhoneNumberContact> phoneNumberContacts) {
 81        for (Contact contact : account.getRoster().getWithSystemAccounts(PhoneNumberContact.class)) {
 82            final Uri uri = contact.getSystemAccount();
 83            if (uri == null) {
 84                continue;
 85            }
 86            final String number = getNumber(gateways, contact);
 87            final PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUriOrNumber(phoneNumberContacts, uri, number);
 88            final boolean needsCacheClean;
 89            if (phoneNumberContact != null) {
 90                if (!uri.equals(phoneNumberContact.getLookupUri())) {
 91                    Log.d(Config.LOGTAG, "lookupUri has changed from " + uri + " to " + phoneNumberContact.getLookupUri());
 92                }
 93                needsCacheClean = contact.setPhoneContact(phoneNumberContact);
 94            } else {
 95                needsCacheClean = contact.unsetPhoneContact(PhoneNumberContact.class);
 96                Log.d(Config.LOGTAG, uri.toString() + " vanished from address book");
 97            }
 98            if (needsCacheClean) {
 99                service.getAvatarService().clear(contact);
100            }
101        }
102    }
103
104    protected void considerSync(boolean forced) {
105        ImmutableMap<String, PhoneNumberContact> allContacts = null;
106        for (final Account account : ImmutableList.copyOf(service.getAccounts())) {
107            final var gateways = gateways(account);
108            if (allContacts == null) allContacts = PhoneNumberContact.load(service);
109            refresh(account, gateways, allContacts.values());
110            if (!considerSync(account, gateways, allContacts, forced)) {
111                account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync();
112            }
113        }
114    }
115
116    protected Set<String> gateways(final Account account) {
117        return Stream.concat(
118            account.getGateways("pstn").stream(),
119            account.getGateways("sms").stream()
120        ).map(a -> a.getJid().asBareJid().toString()).collect(Collectors.toSet());
121    }
122
123    protected boolean considerSync(final Account account, final Set<String> gateways, final Map<String, PhoneNumberContact> contacts, final boolean forced) {
124        final int hash = Objects.hash(contacts.keySet(), gateways);
125        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": consider sync of " + hash);
126        if (!mLastSyncAttempt.getOrDefault(account.getUuid(), Attempt.NULL).retry(hash) && !forced) {
127            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not attempt sync");
128            return false;
129        }
130        mRunningSyncJobs.incrementAndGet();
131
132        mLastSyncAttempt.put(account.getUuid(), Attempt.create(hash));
133        final List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class);
134        for (Map.Entry<String, PhoneNumberContact> item : contacts.entrySet()) {
135            PhoneNumberContact phoneContact = item.getValue();
136            for(String gateway : gateways) {
137                final Jid jid = Jid.ofLocalAndDomain(phoneContact.getPhoneNumber(), gateway);
138                final Contact contact = account.getRoster().getContact(jid);
139                boolean needsCacheClean = contact.setPhoneContact(phoneContact);
140                needsCacheClean |= contact.setSystemTags(phoneContact.getTags());
141                if (needsCacheClean) {
142                    service.getAvatarService().clear(contact);
143                }
144                withSystemAccounts.remove(contact);
145            }
146        }
147        for (final Contact contact : withSystemAccounts) {
148            final boolean needsCacheClean = contact.unsetPhoneContact(PhoneNumberContact.class);
149            if (needsCacheClean) {
150                service.getAvatarService().clear(contact);
151            }
152        }
153
154        mRunningSyncJobs.decrementAndGet();
155        account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync();
156        service.updateRosterUi(XmppConnectionService.UpdateRosterReason.INIT);
157        return true;
158    }
159
160    protected static class Attempt {
161        private final long timestamp;
162        private final int hash;
163
164        private static final Attempt NULL = new Attempt(0, 0);
165
166        private Attempt(long timestamp, int hash) {
167            this.timestamp = timestamp;
168            this.hash = hash;
169        }
170
171        public static Attempt create(int hash) {
172            return new Attempt(SystemClock.elapsedRealtime(), hash);
173        }
174
175        public boolean retry(int hash) {
176            return hash != this.hash || SystemClock.elapsedRealtime() - timestamp >= Config.CONTACT_SYNC_RETRY_INTERVAL;
177        }
178    }
179}