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