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