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