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}