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 long getPgpKeyId() {
300		synchronized (this.keys) {
301			if (this.keys.has("pgp_keyid")) {
302				try {
303					return this.keys.getLong("pgp_keyid");
304				} catch (JSONException e) {
305					return 0;
306				}
307			} else {
308				return 0;
309			}
310		}
311	}
312
313	public void setPgpKeyId(long keyId) {
314		synchronized (this.keys) {
315			try {
316				this.keys.put("pgp_keyid", keyId);
317			} catch (final JSONException ignored) {
318			}
319		}
320	}
321
322	public void setOption(int option) {
323		this.subscription |= 1 << option;
324	}
325
326	public void resetOption(int option) {
327		this.subscription &= ~(1 << option);
328	}
329
330	public boolean getOption(int option) {
331		return ((this.subscription & (1 << option)) != 0);
332	}
333
334	public boolean showInRoster() {
335		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
336					.getOption(Contact.Options.DIRTY_DELETE)))
337			|| (this.getOption(Contact.Options.DIRTY_PUSH));
338	}
339
340	public void parseSubscriptionFromElement(Element item) {
341		String ask = item.getAttribute("ask");
342		String subscription = item.getAttribute("subscription");
343
344		if (subscription != null) {
345			switch (subscription) {
346				case "to":
347					this.resetOption(Options.FROM);
348					this.setOption(Options.TO);
349					break;
350				case "from":
351					this.resetOption(Options.TO);
352					this.setOption(Options.FROM);
353					this.resetOption(Options.PREEMPTIVE_GRANT);
354					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
355					break;
356				case "both":
357					this.setOption(Options.TO);
358					this.setOption(Options.FROM);
359					this.resetOption(Options.PREEMPTIVE_GRANT);
360					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
361					break;
362				case "none":
363					this.resetOption(Options.FROM);
364					this.resetOption(Options.TO);
365					break;
366			}
367		}
368
369		// do NOT override asking if pending push request
370		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
371			if ((ask != null) && (ask.equals("subscribe"))) {
372				this.setOption(Contact.Options.ASKING);
373			} else {
374				this.resetOption(Contact.Options.ASKING);
375			}
376		}
377	}
378
379	public void parseGroupsFromElement(Element item) {
380		this.groups = new JSONArray();
381		for (Element element : item.getChildren()) {
382			if (element.getName().equals("group") && element.getContent() != null) {
383				this.groups.put(element.getContent());
384			}
385		}
386	}
387
388	public Element asElement() {
389		final Element item = new Element("item");
390		item.setAttribute("jid", this.jid.toString());
391		if (this.serverName != null) {
392			item.setAttribute("name", this.serverName);
393		}
394		for (String group : getGroups()) {
395			item.addChild("group").setContent(group);
396		}
397		return item;
398	}
399
400	@Override
401	public int compareTo(final ListItem another) {
402		return this.getDisplayName().compareToIgnoreCase(
403				another.getDisplayName());
404	}
405
406	public Jid getServer() {
407		return Jid.ofDomain(getJid().getDomain());
408	}
409
410	public boolean setAvatar(Avatar avatar) {
411		if (this.avatar != null && this.avatar.equals(avatar)) {
412			return false;
413		} else {
414			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
415				return false;
416			}
417			this.avatar = avatar;
418			return true;
419		}
420	}
421
422	public String getAvatar() {
423		return avatar == null ? null : avatar.getFilename();
424	}
425
426	public boolean mutualPresenceSubscription() {
427		return getOption(Options.FROM) && getOption(Options.TO);
428	}
429
430	@Override
431	public boolean isBlocked() {
432		return getAccount().isBlocked(this);
433	}
434
435	@Override
436	public boolean isDomainBlocked() {
437		return getAccount().isBlocked(Jid.ofDomain(this.getJid().getDomain()));
438	}
439
440	@Override
441	public Jid getBlockedJid() {
442		if (isDomainBlocked()) {
443			return Jid.ofDomain(getJid().getDomain());
444		} else {
445			return getJid();
446		}
447	}
448
449	public boolean isSelf() {
450		return account.getJid().asBareJid().equals(getJid().asBareJid());
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}