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}