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 String[] parts = needle.split("\\s+");
153 if (parts.length > 1) {
154 for(int i = 0; i < parts.length; ++i) {
155 if (!match(parts[i])) {
156 return false;
157 }
158 }
159 return true;
160 } else {
161 return jid.toString().contains(needle) || getDisplayName().toLowerCase().contains(needle) || matchInTag(needle);
162 }
163 }
164
165 private boolean matchInTag(String needle) {
166 for (Tag tag : getTags()) {
167 if (tag.getName().toLowerCase().contains(needle)) {
168 return true;
169 }
170 }
171 return false;
172 }
173
174 public ContentValues getContentValues() {
175 ContentValues values = new ContentValues();
176 values.put(ACCOUNT, accountUuid);
177 values.put(SYSTEMNAME, systemName);
178 values.put(SERVERNAME, serverName);
179 values.put(JID, jid.toString());
180 values.put(OPTIONS, subscription);
181 values.put(SYSTEMACCOUNT, systemAccount);
182 values.put(PHOTOURI, photoUri);
183 values.put(KEYS, keys.toString());
184 values.put(AVATAR, avatar);
185 values.put(LAST_PRESENCE, lastseen.presence);
186 values.put(LAST_TIME, lastseen.time);
187 values.put(GROUPS, groups.toString());
188 return values;
189 }
190
191 public int getSubscription() {
192 return this.subscription;
193 }
194
195 public Account getAccount() {
196 return this.account;
197 }
198
199 public void setAccount(Account account) {
200 this.account = account;
201 this.accountUuid = account.getUuid();
202 }
203
204 public Presences getPresences() {
205 return this.presences;
206 }
207
208 public void setPresences(Presences pres) {
209 this.presences = pres;
210 }
211
212 public void updatePresence(String resource, int status) {
213 this.presences.updatePresence(resource, status);
214 }
215
216 public void removePresence(String resource) {
217 this.presences.removePresence(resource);
218 }
219
220 public void clearPresences() {
221 this.presences.clearPresences();
222 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
223 }
224
225 public int getMostAvailableStatus() {
226 return this.presences.getMostAvailableStatus();
227 }
228
229 public void setPhotoUri(String uri) {
230 this.photoUri = uri;
231 }
232
233 public void setServerName(String serverName) {
234 this.serverName = serverName;
235 }
236
237 public void setSystemName(String systemName) {
238 this.systemName = systemName;
239 }
240
241 public void setPresenceName(String presenceName) {
242 this.presenceName = presenceName;
243 }
244
245 public String getSystemAccount() {
246 return systemAccount;
247 }
248
249 public void setSystemAccount(String account) {
250 this.systemAccount = account;
251 }
252
253 public List<String> getGroups() {
254 ArrayList<String> groups = new ArrayList<String>();
255 for (int i = 0; i < this.groups.length(); ++i) {
256 try {
257 groups.add(this.groups.getString(i));
258 } catch (final JSONException ignored) {
259 }
260 }
261 return groups;
262 }
263
264 public ArrayList<String> getOtrFingerprints() {
265 ArrayList<String> fingerprints = new ArrayList<String>();
266 try {
267 if (this.keys.has("otr_fingerprints")) {
268 JSONArray prints = this.keys
269 .getJSONArray("otr_fingerprints");
270 for (int i = 0; i < prints.length(); ++i) {
271 fingerprints.add(prints.getString(i));
272 }
273 }
274 } catch (final JSONException ignored) {
275
276 }
277 return fingerprints;
278 }
279
280 public boolean addOtrFingerprint(String print) {
281 if (getOtrFingerprints().contains(print)) {
282 return false;
283 }
284 try {
285 JSONArray fingerprints;
286 if (!this.keys.has("otr_fingerprints")) {
287 fingerprints = new JSONArray();
288
289 } else {
290 fingerprints = this.keys.getJSONArray("otr_fingerprints");
291 }
292 fingerprints.put(print);
293 this.keys.put("otr_fingerprints", fingerprints);
294 return true;
295 } catch (final JSONException ignored) {
296 return false;
297 }
298 }
299
300 public long getPgpKeyId() {
301 if (this.keys.has("pgp_keyid")) {
302 try {
303 return this.keys.getLong("pgp_keyid");
304 } catch (JSONException e) {
305 return 0;
306 }
307 } else {
308 return 0;
309 }
310 }
311
312 public void setPgpKeyId(long keyId) {
313 try {
314 this.keys.put("pgp_keyid", keyId);
315 } catch (final JSONException ignored) {
316
317 }
318 }
319
320 public void setOption(int option) {
321 this.subscription |= 1 << option;
322 }
323
324 public void resetOption(int option) {
325 this.subscription &= ~(1 << option);
326 }
327
328 public boolean getOption(int option) {
329 return ((this.subscription & (1 << option)) != 0);
330 }
331
332 public boolean showInRoster() {
333 return (this.getOption(Contact.Options.IN_ROSTER) && (!this
334 .getOption(Contact.Options.DIRTY_DELETE)))
335 || (this.getOption(Contact.Options.DIRTY_PUSH));
336 }
337
338 public void parseSubscriptionFromElement(Element item) {
339 String ask = item.getAttribute("ask");
340 String subscription = item.getAttribute("subscription");
341
342 if (subscription != null) {
343 switch (subscription) {
344 case "to":
345 this.resetOption(Options.FROM);
346 this.setOption(Options.TO);
347 break;
348 case "from":
349 this.resetOption(Options.TO);
350 this.setOption(Options.FROM);
351 this.resetOption(Options.PREEMPTIVE_GRANT);
352 break;
353 case "both":
354 this.setOption(Options.TO);
355 this.setOption(Options.FROM);
356 this.resetOption(Options.PREEMPTIVE_GRANT);
357 break;
358 case "none":
359 this.resetOption(Options.FROM);
360 this.resetOption(Options.TO);
361 break;
362 }
363 }
364
365 // do NOT override asking if pending push request
366 if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
367 if ((ask != null) && (ask.equals("subscribe"))) {
368 this.setOption(Contact.Options.ASKING);
369 } else {
370 this.resetOption(Contact.Options.ASKING);
371 }
372 }
373 }
374
375 public void parseGroupsFromElement(Element item) {
376 this.groups = new JSONArray();
377 for (Element element : item.getChildren()) {
378 if (element.getName().equals("group") && element.getContent() != null) {
379 this.groups.put(element.getContent());
380 }
381 }
382 }
383
384 public Element asElement() {
385 final Element item = new Element("item");
386 item.setAttribute("jid", this.jid.toString());
387 if (this.serverName != null) {
388 item.setAttribute("name", this.serverName);
389 }
390 for (String group : getGroups()) {
391 item.addChild("group").setContent(group);
392 }
393 return item;
394 }
395
396 @Override
397 public int compareTo(final ListItem another) {
398 return this.getDisplayName().compareToIgnoreCase(
399 another.getDisplayName());
400 }
401
402 public Jid getServer() {
403 return getJid().toDomainJid();
404 }
405
406 public boolean setAvatar(String filename) {
407 if (this.avatar != null && this.avatar.equals(filename)) {
408 return false;
409 } else {
410 this.avatar = filename;
411 return true;
412 }
413 }
414
415 public String getAvatar() {
416 return this.avatar;
417 }
418
419 public boolean deleteOtrFingerprint(String fingerprint) {
420 boolean success = false;
421 try {
422 if (this.keys.has("otr_fingerprints")) {
423 JSONArray newPrints = new JSONArray();
424 JSONArray oldPrints = this.keys
425 .getJSONArray("otr_fingerprints");
426 for (int i = 0; i < oldPrints.length(); ++i) {
427 if (!oldPrints.getString(i).equals(fingerprint)) {
428 newPrints.put(oldPrints.getString(i));
429 } else {
430 success = true;
431 }
432 }
433 this.keys.put("otr_fingerprints", newPrints);
434 }
435 return success;
436 } catch (JSONException e) {
437 return false;
438 }
439 }
440
441 public boolean trusted() {
442 return getOption(Options.FROM) && getOption(Options.TO);
443 }
444
445 public String getShareableUri() {
446 if (getOtrFingerprints().size() >= 1) {
447 String otr = getOtrFingerprints().get(0);
448 return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr.replace(" ", "");
449 } else {
450 return "xmpp:" + getJid().toBareJid().toString();
451 }
452 }
453
454 public static class Lastseen {
455 public long time;
456 public String presence;
457
458 public Lastseen() {
459 time = 0;
460 presence = null;
461 }
462
463 public Lastseen(final String presence, final long time) {
464 this.time = time;
465 this.presence = presence;
466 }
467 }
468
469 public class Options {
470 public static final int TO = 0;
471 public static final int FROM = 1;
472 public static final int ASKING = 2;
473 public static final int PREEMPTIVE_GRANT = 3;
474 public static final int IN_ROSTER = 4;
475 public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
476 public static final int DIRTY_PUSH = 6;
477 public static final int DIRTY_DELETE = 7;
478 }
479}