1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5
6import org.json.JSONArray;
7import org.json.JSONException;
8import org.json.JSONObject;
9
10import java.util.ArrayList;
11import java.util.List;
12
13import eu.siacs.conversations.utils.UIHelper;
14import eu.siacs.conversations.xml.Element;
15import eu.siacs.conversations.xmpp.jid.InvalidJidException;
16import eu.siacs.conversations.xmpp.jid.Jid;
17
18public class Contact implements ListItem {
19 public static final String TABLENAME = "contacts";
20
21 public static final String SYSTEMNAME = "systemname";
22 public static final String SERVERNAME = "servername";
23 public static final String JID = "jid";
24 public static final String OPTIONS = "options";
25 public static final String SYSTEMACCOUNT = "systemaccount";
26 public static final String PHOTOURI = "photouri";
27 public static final String KEYS = "pgpkey";
28 public static final String ACCOUNT = "accountUuid";
29 public static final String AVATAR = "avatar";
30 public static final String LAST_PRESENCE = "last_presence";
31 public static final String LAST_TIME = "last_time";
32 public static final String GROUPS = "groups";
33 public Lastseen lastseen = new Lastseen();
34 protected String accountUuid;
35 protected String systemName;
36 protected String serverName;
37 protected String presenceName;
38 protected Jid jid;
39 protected int subscription = 0;
40 protected String systemAccount;
41 protected String photoUri;
42 protected String avatar;
43 protected JSONObject keys = new JSONObject();
44 protected JSONArray groups = new JSONArray();
45 protected Presences presences = new Presences();
46 protected Account account;
47
48 public Contact(final String account, final String systemName, final String serverName,
49 final Jid jid, final int subscription, final String photoUri,
50 final String systemAccount, final String keys, final String avatar, final Lastseen lastseen, final String groups) {
51 this.accountUuid = account;
52 this.systemName = systemName;
53 this.serverName = serverName;
54 this.jid = jid;
55 this.subscription = subscription;
56 this.photoUri = photoUri;
57 this.systemAccount = systemAccount;
58 try {
59 this.keys = (keys == null ? new JSONObject("") : new JSONObject(keys));
60 } catch (JSONException e) {
61 this.keys = new JSONObject();
62 }
63 this.avatar = avatar;
64 try {
65 this.groups = (groups == null ? new JSONArray() : new JSONArray(groups));
66 } catch (JSONException e) {
67 this.groups = new JSONArray();
68 }
69 this.lastseen = lastseen;
70 }
71
72 public Contact(final Jid jid) {
73 this.jid = jid;
74 }
75
76 public static Contact fromCursor(final Cursor cursor) {
77 final Lastseen lastseen = new Lastseen(
78 cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
79 cursor.getLong(cursor.getColumnIndex(LAST_TIME)));
80 final Jid jid;
81 try {
82 jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)));
83 } catch (final InvalidJidException e) {
84 // TODO: Borked DB... handle this somehow?
85 return null;
86 }
87 return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
88 cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
89 cursor.getString(cursor.getColumnIndex(SERVERNAME)),
90 jid,
91 cursor.getInt(cursor.getColumnIndex(OPTIONS)),
92 cursor.getString(cursor.getColumnIndex(PHOTOURI)),
93 cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
94 cursor.getString(cursor.getColumnIndex(KEYS)),
95 cursor.getString(cursor.getColumnIndex(AVATAR)),
96 lastseen,
97 cursor.getString(cursor.getColumnIndex(GROUPS)));
98 }
99
100 public String getDisplayName() {
101 if (this.systemName != null) {
102 return this.systemName;
103 } else if (this.serverName != null) {
104 return this.serverName;
105 } else if (this.presenceName != null) {
106 return this.presenceName;
107 } else if (jid.hasLocalpart()) {
108 return jid.getLocalpart();
109 } else {
110 return jid.getDomainpart();
111 }
112 }
113
114 public String getProfilePhoto() {
115 return this.photoUri;
116 }
117
118 public Jid getJid() {
119 return jid;
120 }
121
122 @Override
123 public List<Tag> getTags() {
124 ArrayList<Tag> tags = new ArrayList<Tag>();
125 for (String group : getGroups()) {
126 tags.add(new Tag(group, UIHelper.getColorForName(group)));
127 }
128 int status = getMostAvailableStatus();
129 switch (getMostAvailableStatus()) {
130 case Presences.CHAT:
131 case Presences.ONLINE:
132 tags.add(new Tag("online", 0xff259b24));
133 break;
134 case Presences.AWAY:
135 tags.add(new Tag("away", 0xffff9800));
136 break;
137 case Presences.XA:
138 tags.add(new Tag("not available", 0xffe51c23));
139 break;
140 case Presences.DND:
141 tags.add(new Tag("dnd", 0xffe51c23));
142 break;
143 }
144 return tags;
145 }
146
147 public boolean match(String needle) {
148 if (needle == null) {
149 return true;
150 }
151 needle = needle.toLowerCase();
152 return jid.toString().contains(needle) || getDisplayName().toLowerCase().contains(needle) || matchInTag(needle);
153 }
154
155 private boolean matchInTag(String needle) {
156 for (Tag tag : getTags()) {
157 if (tag.getName().toLowerCase().contains(needle)) {
158 return true;
159 }
160 }
161 return false;
162 }
163
164 public ContentValues getContentValues() {
165 ContentValues values = new ContentValues();
166 values.put(ACCOUNT, accountUuid);
167 values.put(SYSTEMNAME, systemName);
168 values.put(SERVERNAME, serverName);
169 values.put(JID, jid.toString());
170 values.put(OPTIONS, subscription);
171 values.put(SYSTEMACCOUNT, systemAccount);
172 values.put(PHOTOURI, photoUri);
173 values.put(KEYS, keys.toString());
174 values.put(AVATAR, avatar);
175 values.put(LAST_PRESENCE, lastseen.presence);
176 values.put(LAST_TIME, lastseen.time);
177 values.put(GROUPS, groups.toString());
178 return values;
179 }
180
181 public int getSubscription() {
182 return this.subscription;
183 }
184
185 public Account getAccount() {
186 return this.account;
187 }
188
189 public void setAccount(Account account) {
190 this.account = account;
191 this.accountUuid = account.getUuid();
192 }
193
194 public Presences getPresences() {
195 return this.presences;
196 }
197
198 public void setPresences(Presences pres) {
199 this.presences = pres;
200 }
201
202 public void updatePresence(String resource, int status) {
203 this.presences.updatePresence(resource, status);
204 }
205
206 public void removePresence(String resource) {
207 this.presences.removePresence(resource);
208 }
209
210 public void clearPresences() {
211 this.presences.clearPresences();
212 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
213 }
214
215 public int getMostAvailableStatus() {
216 return this.presences.getMostAvailableStatus();
217 }
218
219 public void setPhotoUri(String uri) {
220 this.photoUri = uri;
221 }
222
223 public void setServerName(String serverName) {
224 this.serverName = serverName;
225 }
226
227 public void setSystemName(String systemName) {
228 this.systemName = systemName;
229 }
230
231 public void setPresenceName(String presenceName) {
232 this.presenceName = presenceName;
233 }
234
235 public String getSystemAccount() {
236 return systemAccount;
237 }
238
239 public void setSystemAccount(String account) {
240 this.systemAccount = account;
241 }
242
243 public List<String> getGroups() {
244 ArrayList<String> groups = new ArrayList<String>();
245 for (int i = 0; i < this.groups.length(); ++i) {
246 try {
247 groups.add(this.groups.getString(i));
248 } catch (final JSONException ignored) {
249 }
250 }
251 return groups;
252 }
253
254 public ArrayList<String> getOtrFingerprints() {
255 ArrayList<String> fingerprints = new ArrayList<String>();
256 try {
257 if (this.keys.has("otr_fingerprints")) {
258 JSONArray prints = this.keys
259 .getJSONArray("otr_fingerprints");
260 for (int i = 0; i < prints.length(); ++i) {
261 fingerprints.add(prints.getString(i));
262 }
263 }
264 } catch (final JSONException ignored) {
265
266 }
267 return fingerprints;
268 }
269
270 public boolean addOtrFingerprint(String print) {
271 if (getOtrFingerprints().contains(print)) {
272 return false;
273 }
274 try {
275 JSONArray fingerprints;
276 if (!this.keys.has("otr_fingerprints")) {
277 fingerprints = new JSONArray();
278
279 } else {
280 fingerprints = this.keys.getJSONArray("otr_fingerprints");
281 }
282 fingerprints.put(print);
283 this.keys.put("otr_fingerprints", fingerprints);
284 return true;
285 } catch (final JSONException ignored) {
286 return false;
287 }
288 }
289
290 public long getPgpKeyId() {
291 if (this.keys.has("pgp_keyid")) {
292 try {
293 return this.keys.getLong("pgp_keyid");
294 } catch (JSONException e) {
295 return 0;
296 }
297 } else {
298 return 0;
299 }
300 }
301
302 public void setPgpKeyId(long keyId) {
303 try {
304 this.keys.put("pgp_keyid", keyId);
305 } catch (final JSONException ignored) {
306
307 }
308 }
309
310 public void setOption(int option) {
311 this.subscription |= 1 << option;
312 }
313
314 public void resetOption(int option) {
315 this.subscription &= ~(1 << option);
316 }
317
318 public boolean getOption(int option) {
319 return ((this.subscription & (1 << option)) != 0);
320 }
321
322 public boolean showInRoster() {
323 return (this.getOption(Contact.Options.IN_ROSTER) && (!this
324 .getOption(Contact.Options.DIRTY_DELETE)))
325 || (this.getOption(Contact.Options.DIRTY_PUSH));
326 }
327
328 public void parseSubscriptionFromElement(Element item) {
329 String ask = item.getAttribute("ask");
330 String subscription = item.getAttribute("subscription");
331
332 if (subscription != null) {
333 switch (subscription) {
334 case "to":
335 this.resetOption(Options.FROM);
336 this.setOption(Options.TO);
337 break;
338 case "from":
339 this.resetOption(Options.TO);
340 this.setOption(Options.FROM);
341 this.resetOption(Options.PREEMPTIVE_GRANT);
342 break;
343 case "both":
344 this.setOption(Options.TO);
345 this.setOption(Options.FROM);
346 this.resetOption(Options.PREEMPTIVE_GRANT);
347 break;
348 case "none":
349 this.resetOption(Options.FROM);
350 this.resetOption(Options.TO);
351 break;
352 }
353 }
354
355 // do NOT override asking if pending push request
356 if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
357 if ((ask != null) && (ask.equals("subscribe"))) {
358 this.setOption(Contact.Options.ASKING);
359 } else {
360 this.resetOption(Contact.Options.ASKING);
361 }
362 }
363 }
364
365 public void parseGroupsFromElement(Element item) {
366 this.groups = new JSONArray();
367 for (Element element : item.getChildren()) {
368 if (element.getName().equals("group") && element.getContent() != null) {
369 this.groups.put(element.getContent());
370 }
371 }
372 }
373
374 public Element asElement() {
375 final Element item = new Element("item");
376 item.setAttribute("jid", this.jid.toString());
377 if (this.serverName != null) {
378 item.setAttribute("name", this.serverName);
379 }
380 for (String group : getGroups()) {
381 item.addChild("group").setContent(group);
382 }
383 return item;
384 }
385
386 @Override
387 public int compareTo(final ListItem another) {
388 return this.getDisplayName().compareToIgnoreCase(
389 another.getDisplayName());
390 }
391
392 public Jid getServer() {
393 return getJid().toDomainJid();
394 }
395
396 public boolean setAvatar(String filename) {
397 if (this.avatar != null && this.avatar.equals(filename)) {
398 return false;
399 } else {
400 this.avatar = filename;
401 return true;
402 }
403 }
404
405 public String getAvatar() {
406 return this.avatar;
407 }
408
409 public boolean deleteOtrFingerprint(String fingerprint) {
410 boolean success = false;
411 try {
412 if (this.keys.has("otr_fingerprints")) {
413 JSONArray newPrints = new JSONArray();
414 JSONArray oldPrints = this.keys
415 .getJSONArray("otr_fingerprints");
416 for (int i = 0; i < oldPrints.length(); ++i) {
417 if (!oldPrints.getString(i).equals(fingerprint)) {
418 newPrints.put(oldPrints.getString(i));
419 } else {
420 success = true;
421 }
422 }
423 this.keys.put("otr_fingerprints", newPrints);
424 }
425 return success;
426 } catch (JSONException e) {
427 return false;
428 }
429 }
430
431 public boolean trusted() {
432 return getOption(Options.FROM) && getOption(Options.TO);
433 }
434
435 public String getShareableUri() {
436 if (getOtrFingerprints().size() >= 1) {
437 String otr = getOtrFingerprints().get(0);
438 return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr.replace(" ", "");
439 } else {
440 return "xmpp:" + getJid().toBareJid().toString();
441 }
442 }
443
444 public static class Lastseen {
445 public long time;
446 public String presence;
447
448 public Lastseen() {
449 time = 0;
450 presence = null;
451 }
452
453 public Lastseen(final String presence, final long time) {
454 this.time = time;
455 this.presence = presence;
456 }
457 }
458
459 public class Options {
460 public static final int TO = 0;
461 public static final int FROM = 1;
462 public static final int ASKING = 2;
463 public static final int PREEMPTIVE_GRANT = 3;
464 public static final int IN_ROSTER = 4;
465 public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
466 public static final int DIRTY_PUSH = 6;
467 public static final int DIRTY_DELETE = 7;
468 }
469}