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