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