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