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 ArrayList<String> fingerprints = new ArrayList<String>();
270 try {
271 if (this.keys.has("otr_fingerprints")) {
272 JSONArray prints = this.keys
273 .getJSONArray("otr_fingerprints");
274 for (int i = 0; i < prints.length(); ++i) {
275 fingerprints.add(prints.getString(i));
276 }
277 }
278 } catch (final JSONException ignored) {
279
280 }
281 return fingerprints;
282 }
283
284 public boolean addOtrFingerprint(String print) {
285 if (getOtrFingerprints().contains(print)) {
286 return false;
287 }
288 try {
289 JSONArray fingerprints;
290 if (!this.keys.has("otr_fingerprints")) {
291 fingerprints = new JSONArray();
292
293 } else {
294 fingerprints = this.keys.getJSONArray("otr_fingerprints");
295 }
296 fingerprints.put(print);
297 this.keys.put("otr_fingerprints", fingerprints);
298 return true;
299 } catch (final JSONException ignored) {
300 return false;
301 }
302 }
303
304 public long getPgpKeyId() {
305 if (this.keys.has("pgp_keyid")) {
306 try {
307 return this.keys.getLong("pgp_keyid");
308 } catch (JSONException e) {
309 return 0;
310 }
311 } else {
312 return 0;
313 }
314 }
315
316 public void setPgpKeyId(long keyId) {
317 try {
318 this.keys.put("pgp_keyid", keyId);
319 } catch (final JSONException ignored) {
320
321 }
322 }
323
324 public void setOption(int option) {
325 this.subscription |= 1 << option;
326 }
327
328 public void resetOption(int option) {
329 this.subscription &= ~(1 << option);
330 }
331
332 public boolean getOption(int option) {
333 return ((this.subscription & (1 << option)) != 0);
334 }
335
336 public boolean showInRoster() {
337 return (this.getOption(Contact.Options.IN_ROSTER) && (!this
338 .getOption(Contact.Options.DIRTY_DELETE)))
339 || (this.getOption(Contact.Options.DIRTY_PUSH));
340 }
341
342 public void parseSubscriptionFromElement(Element item) {
343 String ask = item.getAttribute("ask");
344 String subscription = item.getAttribute("subscription");
345
346 if (subscription != null) {
347 switch (subscription) {
348 case "to":
349 this.resetOption(Options.FROM);
350 this.setOption(Options.TO);
351 break;
352 case "from":
353 this.resetOption(Options.TO);
354 this.setOption(Options.FROM);
355 this.resetOption(Options.PREEMPTIVE_GRANT);
356 break;
357 case "both":
358 this.setOption(Options.TO);
359 this.setOption(Options.FROM);
360 this.resetOption(Options.PREEMPTIVE_GRANT);
361 break;
362 case "none":
363 this.resetOption(Options.FROM);
364 this.resetOption(Options.TO);
365 break;
366 }
367 }
368
369 // do NOT override asking if pending push request
370 if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
371 if ((ask != null) && (ask.equals("subscribe"))) {
372 this.setOption(Contact.Options.ASKING);
373 } else {
374 this.resetOption(Contact.Options.ASKING);
375 }
376 }
377 }
378
379 public void parseGroupsFromElement(Element item) {
380 this.groups = new JSONArray();
381 for (Element element : item.getChildren()) {
382 if (element.getName().equals("group") && element.getContent() != null) {
383 this.groups.put(element.getContent());
384 }
385 }
386 }
387
388 public Element asElement() {
389 final Element item = new Element("item");
390 item.setAttribute("jid", this.jid.toString());
391 if (this.serverName != null) {
392 item.setAttribute("name", this.serverName);
393 }
394 for (String group : getGroups()) {
395 item.addChild("group").setContent(group);
396 }
397 return item;
398 }
399
400 @Override
401 public int compareTo(final ListItem another) {
402 return this.getDisplayName().compareToIgnoreCase(
403 another.getDisplayName());
404 }
405
406 public Jid getServer() {
407 return getJid().toDomainJid();
408 }
409
410 public boolean setAvatar(String filename) {
411 if (this.avatar != null && this.avatar.equals(filename)) {
412 return false;
413 } else {
414 this.avatar = filename;
415 return true;
416 }
417 }
418
419 public String getAvatar() {
420 return this.avatar;
421 }
422
423 public boolean deleteOtrFingerprint(String fingerprint) {
424 boolean success = false;
425 try {
426 if (this.keys.has("otr_fingerprints")) {
427 JSONArray newPrints = new JSONArray();
428 JSONArray oldPrints = this.keys
429 .getJSONArray("otr_fingerprints");
430 for (int i = 0; i < oldPrints.length(); ++i) {
431 if (!oldPrints.getString(i).equals(fingerprint)) {
432 newPrints.put(oldPrints.getString(i));
433 } else {
434 success = true;
435 }
436 }
437 this.keys.put("otr_fingerprints", newPrints);
438 }
439 return success;
440 } catch (JSONException e) {
441 return false;
442 }
443 }
444
445 public boolean trusted() {
446 return getOption(Options.FROM) && getOption(Options.TO);
447 }
448
449 public String getShareableUri() {
450 if (getOtrFingerprints().size() >= 1) {
451 String otr = getOtrFingerprints().get(0);
452 return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr.replace(" ", "");
453 } else {
454 return "xmpp:" + getJid().toBareJid().toString();
455 }
456 }
457
458 public static class Lastseen {
459 public long time;
460 public String presence;
461
462 public Lastseen() {
463 time = 0;
464 presence = null;
465 }
466
467 public Lastseen(final String presence, final long time) {
468 this.time = time;
469 this.presence = presence;
470 }
471 }
472
473 public class Options {
474 public static final int TO = 0;
475 public static final int FROM = 1;
476 public static final int ASKING = 2;
477 public static final int PREEMPTIVE_GRANT = 3;
478 public static final int IN_ROSTER = 4;
479 public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
480 public static final int DIRTY_PUSH = 6;
481 public static final int DIRTY_DELETE = 7;
482 }
483}