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