Contact.java

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