QuickConversationsService.java

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