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