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