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