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