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		final ContentValues values = new ContentValues();
187		values.put(ACCOUNT, accountUuid);
188		values.put(SYSTEMNAME, systemName);
189		values.put(SERVERNAME, serverName);
190		values.put(JID, jid.toString());
191		values.put(OPTIONS, subscription);
192		values.put(SYSTEMACCOUNT, systemAccount);
193		values.put(PHOTOURI, photoUri);
194		values.put(KEYS, keys.toString());
195		values.put(AVATAR, avatar == null ? null : avatar.getFilename());
196		values.put(LAST_PRESENCE, lastseen.presence);
197		values.put(LAST_TIME, lastseen.time);
198		values.put(GROUPS, groups.toString());
199		return values;
200	}
201
202	public int getSubscription() {
203		return this.subscription;
204	}
205
206	public Account getAccount() {
207		return this.account;
208	}
209
210	public void setAccount(Account account) {
211		this.account = account;
212		this.accountUuid = account.getUuid();
213	}
214
215	public Presences getPresences() {
216		return this.presences;
217	}
218
219	public void setPresences(Presences pres) {
220		this.presences = pres;
221	}
222
223	public void updatePresence(final String resource, final int status) {
224		this.presences.updatePresence(resource, status);
225	}
226
227	public void removePresence(final String resource) {
228		this.presences.removePresence(resource);
229	}
230
231	public void clearPresences() {
232		this.presences.clearPresences();
233		this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
234	}
235
236	public int getMostAvailableStatus() {
237		return this.presences.getMostAvailableStatus();
238	}
239
240	public boolean setPhotoUri(String uri) {
241		if (uri != null && !uri.equals(this.photoUri)) {
242			this.photoUri = uri;
243			return true;
244		} else if (this.photoUri != null && uri == null) {
245			this.photoUri = null;
246			return true;
247		} else {
248			return false;
249		}
250	}
251
252	public void setServerName(String serverName) {
253		this.serverName = serverName;
254	}
255
256	public void setSystemName(String systemName) {
257		this.systemName = systemName;
258	}
259
260	public void setPresenceName(String presenceName) {
261		this.presenceName = presenceName;
262	}
263
264	public String getSystemAccount() {
265		return systemAccount;
266	}
267
268	public void setSystemAccount(String account) {
269		this.systemAccount = account;
270	}
271
272	public List<String> getGroups() {
273		ArrayList<String> groups = new ArrayList<String>();
274		for (int i = 0; i < this.groups.length(); ++i) {
275			try {
276				groups.add(this.groups.getString(i));
277			} catch (final JSONException ignored) {
278			}
279		}
280		return groups;
281	}
282
283	public ArrayList<String> getOtrFingerprints() {
284		final ArrayList<String> fingerprints = new ArrayList<String>();
285		try {
286			if (this.keys.has("otr_fingerprints")) {
287				final JSONArray prints = this.keys.getJSONArray("otr_fingerprints");
288				for (int i = 0; i < prints.length(); ++i) {
289					final String print = prints.isNull(i) ? null : prints.getString(i);
290					if (print != null && !print.isEmpty()) {
291						fingerprints.add(prints.getString(i));
292					}
293				}
294			}
295		} catch (final JSONException ignored) {
296
297		}
298		return fingerprints;
299	}
300
301	public boolean addOtrFingerprint(String print) {
302		if (getOtrFingerprints().contains(print)) {
303			return false;
304		}
305		try {
306			JSONArray fingerprints;
307			if (!this.keys.has("otr_fingerprints")) {
308				fingerprints = new JSONArray();
309
310			} else {
311				fingerprints = this.keys.getJSONArray("otr_fingerprints");
312			}
313			fingerprints.put(print);
314			this.keys.put("otr_fingerprints", fingerprints);
315			return true;
316		} catch (final JSONException ignored) {
317			return false;
318		}
319	}
320
321	public long getPgpKeyId() {
322		if (this.keys.has("pgp_keyid")) {
323			try {
324				return this.keys.getLong("pgp_keyid");
325			} catch (JSONException e) {
326				return 0;
327			}
328		} else {
329			return 0;
330		}
331	}
332
333	public void setPgpKeyId(long keyId) {
334		try {
335			this.keys.put("pgp_keyid", keyId);
336		} catch (final JSONException ignored) {
337
338		}
339	}
340
341	public void setOption(int option) {
342		this.subscription |= 1 << option;
343	}
344
345	public void resetOption(int option) {
346		this.subscription &= ~(1 << option);
347	}
348
349	public boolean getOption(int option) {
350		return ((this.subscription & (1 << option)) != 0);
351	}
352
353	public boolean showInRoster() {
354		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
355					.getOption(Contact.Options.DIRTY_DELETE)))
356			|| (this.getOption(Contact.Options.DIRTY_PUSH));
357	}
358
359	public void parseSubscriptionFromElement(Element item) {
360		String ask = item.getAttribute("ask");
361		String subscription = item.getAttribute("subscription");
362
363		if (subscription != null) {
364			switch (subscription) {
365				case "to":
366					this.resetOption(Options.FROM);
367					this.setOption(Options.TO);
368					break;
369				case "from":
370					this.resetOption(Options.TO);
371					this.setOption(Options.FROM);
372					this.resetOption(Options.PREEMPTIVE_GRANT);
373					break;
374				case "both":
375					this.setOption(Options.TO);
376					this.setOption(Options.FROM);
377					this.resetOption(Options.PREEMPTIVE_GRANT);
378					break;
379				case "none":
380					this.resetOption(Options.FROM);
381					this.resetOption(Options.TO);
382					break;
383			}
384		}
385
386		// do NOT override asking if pending push request
387		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
388			if ((ask != null) && (ask.equals("subscribe"))) {
389				this.setOption(Contact.Options.ASKING);
390			} else {
391				this.resetOption(Contact.Options.ASKING);
392			}
393		}
394	}
395
396	public void parseGroupsFromElement(Element item) {
397		this.groups = new JSONArray();
398		for (Element element : item.getChildren()) {
399			if (element.getName().equals("group") && element.getContent() != null) {
400				this.groups.put(element.getContent());
401			}
402		}
403	}
404
405	public Element asElement() {
406		final Element item = new Element("item");
407		item.setAttribute("jid", this.jid.toString());
408		if (this.serverName != null) {
409			item.setAttribute("name", this.serverName);
410		}
411		for (String group : getGroups()) {
412			item.addChild("group").setContent(group);
413		}
414		return item;
415	}
416
417	@Override
418	public int compareTo(final ListItem another) {
419		return this.getDisplayName().compareToIgnoreCase(
420				another.getDisplayName());
421	}
422
423	public Jid getServer() {
424		return getJid().toDomainJid();
425	}
426
427	public boolean setAvatar(Avatar avatar) {
428		if (this.avatar != null && this.avatar.equals(avatar)) {
429			return false;
430		} else {
431			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
432				return false;
433			}
434			this.avatar = avatar;
435			return true;
436		}
437	}
438
439	public String getAvatar() {
440		return avatar == null ? null : avatar.getFilename();
441	}
442
443	public boolean deleteOtrFingerprint(String fingerprint) {
444		boolean success = false;
445		try {
446			if (this.keys.has("otr_fingerprints")) {
447				JSONArray newPrints = new JSONArray();
448				JSONArray oldPrints = this.keys
449					.getJSONArray("otr_fingerprints");
450				for (int i = 0; i < oldPrints.length(); ++i) {
451					if (!oldPrints.getString(i).equals(fingerprint)) {
452						newPrints.put(oldPrints.getString(i));
453					} else {
454						success = true;
455					}
456				}
457				this.keys.put("otr_fingerprints", newPrints);
458			}
459			return success;
460		} catch (JSONException e) {
461			return false;
462		}
463	}
464
465	public boolean trusted() {
466		return getOption(Options.FROM) && getOption(Options.TO);
467	}
468
469	public String getShareableUri() {
470		if (getOtrFingerprints().size() >= 1) {
471			String otr = getOtrFingerprints().get(0);
472			return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr;
473		} else {
474			return "xmpp:" + getJid().toBareJid().toString();
475		}
476	}
477
478	@Override
479	public boolean isBlocked() {
480		return getAccount().isBlocked(this);
481	}
482
483	@Override
484	public boolean isDomainBlocked() {
485		return getAccount().isBlocked(this.getJid().toDomainJid());
486	}
487
488	@Override
489	public Jid getBlockedJid() {
490		if (isDomainBlocked()) {
491			return getJid().toDomainJid();
492		} else {
493			return getJid();
494		}
495	}
496
497	public boolean isSelf() {
498		return account.getJid().toBareJid().equals(getJid().toBareJid());
499	}
500
501	public static class Lastseen {
502		public long time;
503		public String presence;
504
505		public Lastseen() {
506			this(null, 0);
507		}
508
509		public Lastseen(final String presence, final long time) {
510			this.presence = presence;
511			this.time = time;
512		}
513	}
514
515	public final class Options {
516		public static final int TO = 0;
517		public static final int FROM = 1;
518		public static final int ASKING = 2;
519		public static final int PREEMPTIVE_GRANT = 3;
520		public static final int IN_ROSTER = 4;
521		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
522		public static final int DIRTY_PUSH = 6;
523		public static final int DIRTY_DELETE = 7;
524	}
525}