Contact.java

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