Contact.java

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