Contact.java

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