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