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