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	public String getProfilePhoto() {
131		return this.photoUri;
132	}
133
134	public Jid getJid() {
135		return jid;
136	}
137
138	@Override
139	public List<Tag> getTags(Context context) {
140		final ArrayList<Tag> tags = new ArrayList<>();
141		for (final String group : getGroups()) {
142			tags.add(new Tag(group, UIHelper.getColorForName(group)));
143		}
144		Presence.Status status = getShownStatus();
145		if (status != Presence.Status.OFFLINE) {
146			tags.add(UIHelper.getTagForStatus(context, status));
147		}
148		if (isBlocked()) {
149			tags.add(new Tag("blocked", 0xff2e2f3b));
150		}
151		return tags;
152	}
153
154	public boolean match(Context context, String needle) {
155		if (needle == null || needle.isEmpty()) {
156			return true;
157		}
158		needle = needle.toLowerCase(Locale.US).trim();
159		String[] parts = needle.split("\\s+");
160		if (parts.length > 1) {
161			for(int i = 0; i < parts.length; ++i) {
162				if (!match(context, parts[i])) {
163					return false;
164				}
165			}
166			return true;
167		} else {
168			return jid.toString().contains(needle) ||
169				getDisplayName().toLowerCase(Locale.US).contains(needle) ||
170				matchInTag(context, needle);
171		}
172	}
173
174	private boolean matchInTag(Context context, String needle) {
175		needle = needle.toLowerCase(Locale.US);
176		for (Tag tag : getTags(context)) {
177			if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
178				return true;
179			}
180		}
181		return false;
182	}
183
184	public ContentValues getContentValues() {
185		synchronized (this.keys) {
186			final ContentValues values = new ContentValues();
187			values.put(ACCOUNT, accountUuid);
188			values.put(SYSTEMNAME, systemName);
189			values.put(SERVERNAME, serverName);
190			values.put(JID, jid.toString());
191			values.put(OPTIONS, subscription);
192			values.put(SYSTEMACCOUNT, systemAccount);
193			values.put(PHOTOURI, photoUri);
194			values.put(KEYS, keys.toString());
195			values.put(AVATAR, avatar == null ? null : avatar.getFilename());
196			values.put(LAST_PRESENCE, mLastPresence);
197			values.put(LAST_TIME, mLastseen);
198			values.put(GROUPS, groups.toString());
199			return values;
200		}
201	}
202
203	public Account getAccount() {
204		return this.account;
205	}
206
207	public void setAccount(Account account) {
208		this.account = account;
209		this.accountUuid = account.getUuid();
210	}
211
212	public Presences getPresences() {
213		return this.presences;
214	}
215
216	public void updatePresence(final String resource, final Presence presence) {
217		this.presences.updatePresence(resource, presence);
218	}
219
220	public void removePresence(final String resource) {
221		this.presences.removePresence(resource);
222	}
223
224	public void clearPresences() {
225		this.presences.clearPresences();
226		this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
227	}
228
229	public Presence.Status getShownStatus() {
230		return this.presences.getShownStatus();
231	}
232
233	public boolean setPhotoUri(String uri) {
234		if (uri != null && !uri.equals(this.photoUri)) {
235			this.photoUri = uri;
236			return true;
237		} else if (this.photoUri != null && uri == null) {
238			this.photoUri = null;
239			return true;
240		} else {
241			return false;
242		}
243	}
244
245	public void setServerName(String serverName) {
246		this.serverName = serverName;
247	}
248
249	public boolean setSystemName(String systemName) {
250		final String old = getDisplayName();
251		this.systemName = systemName;
252		return !old.equals(getDisplayName());
253	}
254
255	public boolean setPresenceName(String presenceName) {
256		final String old = getDisplayName();
257		this.presenceName = presenceName;
258		return !old.equals(getDisplayName());
259	}
260
261	public Uri getSystemAccount() {
262		if (systemAccount == null) {
263			return null;
264		} else {
265			String[] parts = systemAccount.split("#");
266			if (parts.length != 2) {
267				return null;
268			} else {
269				long id = Long.parseLong(parts[0]);
270				return ContactsContract.Contacts.getLookupUri(id, parts[1]);
271			}
272		}
273	}
274
275	public void setSystemAccount(String account) {
276		this.systemAccount = account;
277	}
278
279	public List<String> getGroups() {
280		ArrayList<String> groups = new ArrayList<String>();
281		for (int i = 0; i < this.groups.length(); ++i) {
282			try {
283				groups.add(this.groups.getString(i));
284			} catch (final JSONException ignored) {
285			}
286		}
287		return groups;
288	}
289
290	public long getPgpKeyId() {
291		synchronized (this.keys) {
292			if (this.keys.has("pgp_keyid")) {
293				try {
294					return this.keys.getLong("pgp_keyid");
295				} catch (JSONException e) {
296					return 0;
297				}
298			} else {
299				return 0;
300			}
301		}
302	}
303
304	public void setPgpKeyId(long keyId) {
305		synchronized (this.keys) {
306			try {
307				this.keys.put("pgp_keyid", keyId);
308			} catch (final JSONException ignored) {
309			}
310		}
311	}
312
313	public void setOption(int option) {
314		this.subscription |= 1 << option;
315	}
316
317	public void resetOption(int option) {
318		this.subscription &= ~(1 << option);
319	}
320
321	public boolean getOption(int option) {
322		return ((this.subscription & (1 << option)) != 0);
323	}
324
325	public boolean showInRoster() {
326		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
327					.getOption(Contact.Options.DIRTY_DELETE)))
328			|| (this.getOption(Contact.Options.DIRTY_PUSH));
329	}
330
331	public void parseSubscriptionFromElement(Element item) {
332		String ask = item.getAttribute("ask");
333		String subscription = item.getAttribute("subscription");
334
335		if (subscription != null) {
336			switch (subscription) {
337				case "to":
338					this.resetOption(Options.FROM);
339					this.setOption(Options.TO);
340					break;
341				case "from":
342					this.resetOption(Options.TO);
343					this.setOption(Options.FROM);
344					this.resetOption(Options.PREEMPTIVE_GRANT);
345					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
346					break;
347				case "both":
348					this.setOption(Options.TO);
349					this.setOption(Options.FROM);
350					this.resetOption(Options.PREEMPTIVE_GRANT);
351					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
352					break;
353				case "none":
354					this.resetOption(Options.FROM);
355					this.resetOption(Options.TO);
356					break;
357			}
358		}
359
360		// do NOT override asking if pending push request
361		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
362			if ((ask != null) && (ask.equals("subscribe"))) {
363				this.setOption(Contact.Options.ASKING);
364			} else {
365				this.resetOption(Contact.Options.ASKING);
366			}
367		}
368	}
369
370	public void parseGroupsFromElement(Element item) {
371		this.groups = new JSONArray();
372		for (Element element : item.getChildren()) {
373			if (element.getName().equals("group") && element.getContent() != null) {
374				this.groups.put(element.getContent());
375			}
376		}
377	}
378
379	public Element asElement() {
380		final Element item = new Element("item");
381		item.setAttribute("jid", this.jid.toString());
382		if (this.serverName != null) {
383			item.setAttribute("name", this.serverName);
384		}
385		for (String group : getGroups()) {
386			item.addChild("group").setContent(group);
387		}
388		return item;
389	}
390
391	@Override
392	public int compareTo(final ListItem another) {
393		return this.getDisplayName().compareToIgnoreCase(
394				another.getDisplayName());
395	}
396
397	public String getServer() {
398		return getJid().getDomain();
399	}
400
401	public boolean setAvatar(Avatar avatar) {
402		if (this.avatar != null && this.avatar.equals(avatar)) {
403			return false;
404		} else {
405			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
406				return false;
407			}
408			this.avatar = avatar;
409			return true;
410		}
411	}
412
413	public String getAvatar() {
414		return avatar == null ? null : avatar.getFilename();
415	}
416
417	public boolean mutualPresenceSubscription() {
418		return getOption(Options.FROM) && getOption(Options.TO);
419	}
420
421	@Override
422	public boolean isBlocked() {
423		return getAccount().isBlocked(this);
424	}
425
426	@Override
427	public boolean isDomainBlocked() {
428		return getAccount().isBlocked(Jid.ofDomain(this.getJid().getDomain()));
429	}
430
431	@Override
432	public Jid getBlockedJid() {
433		if (isDomainBlocked()) {
434			return Jid.ofDomain(getJid().getDomain());
435		} else {
436			return getJid();
437		}
438	}
439
440	public boolean isSelf() {
441		return account.getJid().asBareJid().equals(getJid().asBareJid());
442	}
443
444	public void setCommonName(String cn) {
445		this.commonName = cn;
446	}
447
448	public void flagActive() {
449		this.mActive = true;
450	}
451
452	public void flagInactive() {
453		this.mActive = false;
454	}
455
456	public boolean isActive() {
457		return this.mActive;
458	}
459
460	public boolean setLastseen(long timestamp) {
461		if (timestamp > this.mLastseen) {
462			this.mLastseen = timestamp;
463			return true;
464		} else {
465			return false;
466		}
467	}
468
469	public long getLastseen() {
470		return this.mLastseen;
471	}
472
473	public void setLastResource(String resource) {
474		this.mLastPresence = resource;
475	}
476
477	public String getLastResource() {
478		return this.mLastPresence;
479	}
480
481	public final class Options {
482		public static final int TO = 0;
483		public static final int FROM = 1;
484		public static final int ASKING = 2;
485		public static final int PREEMPTIVE_GRANT = 3;
486		public static final int IN_ROSTER = 4;
487		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
488		public static final int DIRTY_PUSH = 6;
489		public static final int DIRTY_DELETE = 7;
490	}
491}