MucOptions.java

  1package eu.siacs.conversations.entities;
  2
  3import android.annotation.SuppressLint;
  4
  5import java.util.ArrayList;
  6import java.util.Collections;
  7import java.util.HashSet;
  8import java.util.List;
  9import java.util.Set;
 10
 11import eu.siacs.conversations.R;
 12import eu.siacs.conversations.xmpp.forms.Data;
 13import eu.siacs.conversations.xmpp.forms.Field;
 14import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 15import eu.siacs.conversations.xmpp.jid.Jid;
 16import eu.siacs.conversations.xmpp.pep.Avatar;
 17
 18@SuppressLint("DefaultLocale")
 19public class MucOptions {
 20
 21	public Account getAccount() {
 22		return this.conversation.getAccount();
 23	}
 24
 25	public void setSelf(User user) {
 26		this.self = user;
 27	}
 28
 29	public void changeAffiliation(Jid jid, Affiliation affiliation) {
 30		User user = findUserByRealJid(jid);
 31		synchronized (users) {
 32			if (user != null && user.getRole() == Role.NONE) {
 33				users.remove(user);
 34				if (affiliation.ranks(Affiliation.MEMBER)) {
 35					user.affiliation = affiliation;
 36					users.add(user);
 37				}
 38			}
 39		}
 40	}
 41
 42	public enum Affiliation {
 43		OWNER("owner", 4, R.string.owner),
 44		ADMIN("admin", 3, R.string.admin),
 45		MEMBER("member", 2, R.string.member),
 46		OUTCAST("outcast", 0, R.string.outcast),
 47		NONE("none", 1, R.string.no_affiliation);
 48
 49		Affiliation(String string, int rank, int resId) {
 50			this.string = string;
 51			this.resId = resId;
 52			this.rank = rank;
 53		}
 54
 55		private String string;
 56		private int resId;
 57		private int rank;
 58
 59		public int getResId() {
 60			return resId;
 61		}
 62
 63		@Override
 64		public String toString() {
 65			return this.string;
 66		}
 67
 68		public boolean outranks(Affiliation affiliation) {
 69			return rank > affiliation.rank;
 70		}
 71
 72		public boolean ranks(Affiliation affiliation) {
 73			return rank >= affiliation.rank;
 74		}
 75	}
 76
 77	public enum Role {
 78		MODERATOR("moderator", R.string.moderator,3),
 79		VISITOR("visitor", R.string.visitor,1),
 80		PARTICIPANT("participant", R.string.participant,2),
 81		NONE("none", R.string.no_role,0);
 82
 83		Role(String string, int resId, int rank) {
 84			this.string = string;
 85			this.resId = resId;
 86			this.rank = rank;
 87		}
 88
 89		private String string;
 90		private int resId;
 91		private int rank;
 92
 93		public int getResId() {
 94			return resId;
 95		}
 96
 97		@Override
 98		public String toString() {
 99			return this.string;
100		}
101
102		public boolean ranks(Role role) {
103			return rank >= role.rank;
104		}
105	}
106
107	public enum Error {
108		NO_RESPONSE,
109		NONE,
110		NICK_IN_USE,
111		PASSWORD_REQUIRED,
112		BANNED,
113		MEMBERS_ONLY,
114		KICKED,
115		SHUTDOWN,
116		UNKNOWN
117	}
118
119	public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
120	public static final String STATUS_CODE_SELF_PRESENCE = "110";
121	public static final String STATUS_CODE_ROOM_CREATED = "201";
122	public static final String STATUS_CODE_BANNED = "301";
123	public static final String STATUS_CODE_CHANGED_NICK = "303";
124	public static final String STATUS_CODE_KICKED = "307";
125	public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
126	public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
127	public static final String STATUS_CODE_SHUTDOWN = "332";
128
129	private interface OnEventListener {
130		void onSuccess();
131
132		void onFailure();
133	}
134
135	public interface OnRenameListener extends OnEventListener {
136
137	}
138
139	public static class User implements Comparable<User> {
140		private Role role = Role.NONE;
141		private Affiliation affiliation = Affiliation.NONE;
142		private Jid realJid;
143		private Jid fullJid;
144		private long pgpKeyId = 0;
145		private Avatar avatar;
146		private MucOptions options;
147
148		public User(MucOptions options, Jid from) {
149			this.options = options;
150			this.fullJid = from;
151		}
152
153		public String getName() {
154			return fullJid == null ? null : fullJid.getResourcepart();
155		}
156
157		public void setRealJid(Jid jid) {
158			this.realJid = jid != null ? jid.toBareJid() : null;
159		}
160
161		public Role getRole() {
162			return this.role;
163		}
164
165		public void setRole(String role) {
166			if (role == null) {
167				this.role = Role.NONE;
168				return;
169			}
170			role = role.toLowerCase();
171			switch (role) {
172				case "moderator":
173					this.role = Role.MODERATOR;
174					break;
175				case "participant":
176					this.role = Role.PARTICIPANT;
177					break;
178				case "visitor":
179					this.role = Role.VISITOR;
180					break;
181				default:
182					this.role = Role.NONE;
183					break;
184			}
185		}
186
187		public Affiliation getAffiliation() {
188			return this.affiliation;
189		}
190
191		public void setAffiliation(String affiliation) {
192			if (affiliation == null) {
193				this.affiliation = Affiliation.NONE;
194				return;
195			}
196			affiliation = affiliation.toLowerCase();
197			switch (affiliation) {
198				case "admin":
199					this.affiliation = Affiliation.ADMIN;
200					break;
201				case "owner":
202					this.affiliation = Affiliation.OWNER;
203					break;
204				case "member":
205					this.affiliation = Affiliation.MEMBER;
206					break;
207				case "outcast":
208					this.affiliation = Affiliation.OUTCAST;
209					break;
210				default:
211					this.affiliation = Affiliation.NONE;
212			}
213		}
214
215		public void setPgpKeyId(long id) {
216			this.pgpKeyId = id;
217		}
218
219		public long getPgpKeyId() {
220			return this.pgpKeyId;
221		}
222
223		public Contact getContact() {
224			if (fullJid != null) {
225				return getAccount().getRoster().getContactFromRoster(realJid);
226			} else if (realJid != null){
227				return getAccount().getRoster().getContact(realJid);
228			} else {
229				return null;
230			}
231		}
232
233		public boolean setAvatar(Avatar avatar) {
234			if (this.avatar != null && this.avatar.equals(avatar)) {
235				return false;
236			} else {
237				this.avatar = avatar;
238				return true;
239			}
240		}
241
242		public String getAvatar() {
243			return avatar == null ? null : avatar.getFilename();
244		}
245
246		public Account getAccount() {
247			return options.getAccount();
248		}
249
250		public Jid getFullJid() {
251			return fullJid;
252		}
253
254		@Override
255		public boolean equals(Object o) {
256			if (this == o) return true;
257			if (o == null || getClass() != o.getClass()) return false;
258
259			User user = (User) o;
260
261			if (role != user.role) return false;
262			if (affiliation != user.affiliation) return false;
263			if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null)
264				return false;
265			return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null;
266
267		}
268
269		@Override
270		public int hashCode() {
271			int result = role != null ? role.hashCode() : 0;
272			result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0);
273			result = 31 * result + (realJid != null ? realJid.hashCode() : 0);
274			result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0);
275			return result;
276		}
277
278		@Override
279		public String toString() {
280			return "[fulljid:"+String.valueOf(fullJid)+",realjid:"+String.valueOf(realJid)+",affiliation"+affiliation.toString()+"]";
281		}
282
283		public boolean realJidMatchesAccount() {
284			return realJid != null && realJid.equals(options.account.getJid().toBareJid());
285		}
286
287		@Override
288		public int compareTo(User another) {
289			Contact ourContact = getContact();
290			Contact anotherContact = another.getContact();
291			if (ourContact != null && anotherContact != null) {
292				return ourContact.compareTo(anotherContact);
293			} else if (ourContact == null && anotherContact != null) {
294				return getName().compareToIgnoreCase(anotherContact.getDisplayName());
295			} else if (ourContact != null) {
296				return ourContact.getDisplayName().compareToIgnoreCase(another.getName());
297			} else {
298				return getName().compareToIgnoreCase(another.getName());
299			}
300		}
301
302		public Jid getRealJid() {
303			return realJid;
304		}
305	}
306
307	private Account account;
308	private final Set<User> users = new HashSet<>();
309	private final List<String> features = new ArrayList<>();
310	private Data form = new Data();
311	private Conversation conversation;
312	private boolean isOnline = false;
313	private Error error = Error.NONE;
314	public OnRenameListener onRenameListener = null;
315	private User self;
316	private String subject = null;
317	private String password = null;
318	public boolean mNickChangingInProgress = false;
319
320	public MucOptions(Conversation conversation) {
321		this.account = conversation.getAccount();
322		this.conversation = conversation;
323		this.self = new User(this,createJoinJid(getProposedNick()));
324	}
325
326	public void updateFeatures(ArrayList<String> features) {
327		this.features.clear();
328		this.features.addAll(features);
329	}
330
331	public void updateFormData(Data form) {
332		this.form = form;
333	}
334
335	public boolean hasFeature(String feature) {
336		return this.features.contains(feature);
337	}
338
339	public boolean canInvite() {
340		Field field = this.form.getFieldByName("muc#roomconfig_allowinvites");
341		return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
342	}
343
344	public boolean canChangeSubject() {
345		Field field = this.form.getFieldByName("muc#roomconfig_changesubject");
346		return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
347	}
348
349	public boolean participating() {
350		return !online()
351				|| self.getRole().ranks(Role.PARTICIPANT)
352				|| hasFeature("muc_unmoderated");
353	}
354
355	public boolean membersOnly() {
356		return hasFeature("muc_membersonly");
357	}
358
359	public boolean mamSupport() {
360		// Update with "urn:xmpp:mam:1" once we support it
361		return hasFeature("urn:xmpp:mam:0");
362	}
363
364	public boolean nonanonymous() {
365		return hasFeature("muc_nonanonymous");
366	}
367
368	public boolean persistent() {
369		return hasFeature("muc_persistent");
370	}
371
372	public boolean moderated() {
373		return hasFeature("muc_moderated");
374	}
375
376	public User deleteUser(Jid jid) {
377		User user = findUserByFullJid(jid);
378		if (user != null) {
379			synchronized (users) {
380				users.remove(user);
381				if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) {
382					user.role = Role.NONE;
383					user.avatar = null;
384					user.fullJid = null;
385					users.add(user);
386				}
387			}
388		}
389		return user;
390	}
391
392	public void addUser(User user) {
393		User old;
394		if (user.fullJid == null && user.realJid != null) {
395			old = findUserByRealJid(user.realJid);
396			if (old != null) {
397				if (old.fullJid != null) {
398					return; //don't add. user already exists
399				} else {
400					synchronized (users) {
401						users.remove(old);
402					}
403				}
404			}
405		} else if (user.realJid != null) {
406			old = findUserByRealJid(user.realJid);
407			synchronized (users) {
408				if (old != null && old.fullJid == null) {
409					users.remove(old);
410				}
411			}
412		}
413		old = findUserByFullJid(user.getFullJid());
414		synchronized (this.users) {
415			if (old != null) {
416				users.remove(old);
417			}
418			this.users.add(user);
419		}
420	}
421
422	public User findUserByFullJid(Jid jid) {
423		if (jid == null) {
424			return null;
425		}
426		synchronized (users) {
427			for (User user : users) {
428				if (jid.equals(user.getFullJid())) {
429					return user;
430				}
431			}
432		}
433		return null;
434	}
435
436	public User findUserByRealJid(Jid jid) {
437		if (jid == null) {
438			return null;
439		}
440		synchronized (users) {
441			for (User user : users) {
442				if (jid.equals(user.realJid)) {
443					return user;
444				}
445			}
446		}
447		return null;
448	}
449
450	public boolean isUserInRoom(Jid jid) {
451		return findUserByFullJid(jid) != null;
452	}
453
454	public void setError(Error error) {
455		this.isOnline = isOnline && error == Error.NONE;
456		this.error = error;
457	}
458
459	public void setOnline() {
460		this.isOnline = true;
461	}
462
463	public ArrayList<User> getUsers() {
464		return getUsers(true);
465	}
466
467	public ArrayList<User> getUsers(boolean includeOffline) {
468		synchronized (users) {
469			if (includeOffline) {
470				return new ArrayList<>(users);
471			} else {
472				ArrayList<User> onlineUsers = new ArrayList<>();
473				for (User user : users) {
474					if (user.getRole().ranks(Role.PARTICIPANT)) {
475						onlineUsers.add(user);
476					}
477				}
478				return onlineUsers;
479			}
480		}
481	}
482
483	public List<User> getUsers(int max) {
484		ArrayList<User> users = getUsers();
485		return users.subList(0, Math.min(max, users.size()));
486	}
487
488	public int getUserCount() {
489		synchronized (users) {
490			return users.size();
491		}
492	}
493
494	public String getProposedNick() {
495		if (conversation.getBookmark() != null
496				&& conversation.getBookmark().getNick() != null
497				&& !conversation.getBookmark().getNick().isEmpty()) {
498			return conversation.getBookmark().getNick();
499		} else if (!conversation.getJid().isBareJid()) {
500			return conversation.getJid().getResourcepart();
501		} else {
502			return account.getUsername();
503		}
504	}
505
506	public String getActualNick() {
507		if (this.self.getName() != null) {
508			return this.self.getName();
509		} else {
510			return this.getProposedNick();
511		}
512	}
513
514	public boolean online() {
515		return this.isOnline;
516	}
517
518	public Error getError() {
519		return this.error;
520	}
521
522	public void setOnRenameListener(OnRenameListener listener) {
523		this.onRenameListener = listener;
524	}
525
526	public void setOffline() {
527		synchronized (users) {
528			this.users.clear();
529		}
530		this.error = Error.NO_RESPONSE;
531		this.isOnline = false;
532	}
533
534	public User getSelf() {
535		return self;
536	}
537
538	public void setSubject(String content) {
539		this.subject = content;
540	}
541
542	public String getSubject() {
543		return this.subject;
544	}
545
546	public String createNameFromParticipants() {
547		if (getUserCount() >= 2) {
548			List<String> names = new ArrayList<>();
549			for (User user : getUsers(5)) {
550				Contact contact = user.getContact();
551				if (contact != null && !contact.getDisplayName().isEmpty()) {
552					names.add(contact.getDisplayName().split("\\s+")[0]);
553				} else if (user.getName() != null){
554					names.add(user.getName());
555				}
556			}
557			StringBuilder builder = new StringBuilder();
558			for (int i = 0; i < names.size(); ++i) {
559				builder.append(names.get(i));
560				if (i != names.size() - 1) {
561					builder.append(", ");
562				}
563			}
564			return builder.toString();
565		} else {
566			return null;
567		}
568	}
569
570	public long[] getPgpKeyIds() {
571		List<Long> ids = new ArrayList<>();
572		for (User user : this.users) {
573			if (user.getPgpKeyId() != 0) {
574				ids.add(user.getPgpKeyId());
575			}
576		}
577		ids.add(account.getPgpId());
578		long[] primitiveLongArray = new long[ids.size()];
579		for (int i = 0; i < ids.size(); ++i) {
580			primitiveLongArray[i] = ids.get(i);
581		}
582		return primitiveLongArray;
583	}
584
585	public boolean pgpKeysInUse() {
586		synchronized (users) {
587			for (User user : users) {
588				if (user.getPgpKeyId() != 0) {
589					return true;
590				}
591			}
592		}
593		return false;
594	}
595
596	public boolean everybodyHasKeys() {
597		synchronized (users) {
598			for (User user : users) {
599				if (user.getPgpKeyId() == 0) {
600					return false;
601				}
602			}
603		}
604		return true;
605	}
606
607	public Jid createJoinJid(String nick) {
608		try {
609			return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
610		} catch (final InvalidJidException e) {
611			return null;
612		}
613	}
614
615	public Jid getTrueCounterpart(Jid jid) {
616		if (jid.equals(getSelf().getFullJid())) {
617			return account.getJid().toBareJid();
618		}
619		User user = findUserByFullJid(jid);
620		return user == null ? null : user.realJid;
621	}
622
623	public String getPassword() {
624		this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
625		if (this.password == null && conversation.getBookmark() != null
626				&& conversation.getBookmark().getPassword() != null) {
627			return conversation.getBookmark().getPassword();
628		} else {
629			return this.password;
630		}
631	}
632
633	public void setPassword(String password) {
634		if (conversation.getBookmark() != null) {
635			conversation.getBookmark().setPassword(password);
636		} else {
637			this.password = password;
638		}
639		conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
640	}
641
642	public Conversation getConversation() {
643		return this.conversation;
644	}
645
646	public List<Jid> getMembers() {
647		ArrayList<Jid> members = new ArrayList<>();
648		synchronized (users) {
649			for (User user : users) {
650				if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) {
651					members.add(user.realJid);
652				}
653			}
654		}
655		return members;
656	}
657}