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