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