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