MucOptions.java

  1package eu.siacs.conversations.entities;
  2
  3import java.util.ArrayList;
  4import java.util.List;
  5import java.util.concurrent.CopyOnWriteArrayList;
  6
  7import eu.siacs.conversations.Config;
  8import eu.siacs.conversations.R;
  9import eu.siacs.conversations.crypto.PgpEngine;
 10import eu.siacs.conversations.xml.Element;
 11import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 12import eu.siacs.conversations.xmpp.jid.Jid;
 13import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
 14
 15import android.annotation.SuppressLint;
 16import android.util.Log;
 17
 18@SuppressLint("DefaultLocale")
 19public class MucOptions {
 20
 21	public enum Affiliation {
 22		OWNER("owner", 4, R.string.owner),
 23		ADMIN("admin", 3, R.string.admin),
 24		MEMBER("member", 2, R.string.member),
 25		OUTCAST("outcast", 0, R.string.outcast),
 26		NONE("none", 1, R.string.no_affiliation);
 27
 28		private Affiliation(String string, int rank, int resId) {
 29			this.string = string;
 30			this.resId = resId;
 31			this.rank = rank;
 32		}
 33
 34		private String string;
 35		private int resId;
 36		private int rank;
 37
 38		public int getResId() {
 39			return resId;
 40		}
 41
 42		@Override
 43		public String toString() {
 44			return this.string;
 45		}
 46
 47		public boolean outranks(Affiliation affiliation) {
 48			return rank > affiliation.rank;
 49		}
 50
 51		public boolean ranks(Affiliation affiliation) {
 52			return rank >= affiliation.rank;
 53		}
 54	}
 55
 56	public enum Role {
 57		MODERATOR("moderator", R.string.moderator),
 58		VISITOR("visitor", R.string.visitor),
 59		PARTICIPANT("participant", R.string.participant),
 60		NONE("none", R.string.no_role);
 61
 62		private Role(String string, int resId) {
 63			this.string = string;
 64			this.resId = resId;
 65		}
 66
 67		private String string;
 68		private int resId;
 69
 70		public int getResId() {
 71			return resId;
 72		}
 73
 74		@Override
 75		public String toString() {
 76			return this.string;
 77		}
 78	}
 79
 80	public static final int ERROR_NO_ERROR = 0;
 81	public static final int ERROR_NICK_IN_USE = 1;
 82	public static final int ERROR_UNKNOWN = 2;
 83	public static final int ERROR_PASSWORD_REQUIRED = 3;
 84	public static final int ERROR_BANNED = 4;
 85	public static final int ERROR_MEMBERS_ONLY = 5;
 86
 87	public static final int KICKED_FROM_ROOM = 9;
 88
 89	public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
 90	public static final String STATUS_CODE_SELF_PRESENCE = "110";
 91	public static final String STATUS_CODE_BANNED = "301";
 92	public static final String STATUS_CODE_CHANGED_NICK = "303";
 93	public static final String STATUS_CODE_KICKED = "307";
 94	public static final String STATUS_CODE_LOST_MEMBERSHIP = "321";
 95
 96	private interface OnEventListener {
 97		public void onSuccess();
 98
 99		public void onFailure();
100	}
101
102	public interface OnRenameListener extends OnEventListener {
103
104	}
105
106	public interface OnJoinListener extends OnEventListener {
107
108	}
109
110	public class User {
111		private Role role = Role.NONE;
112		private Affiliation affiliation = Affiliation.NONE;
113		private String name;
114		private Jid jid;
115		private long pgpKeyId = 0;
116
117		public String getName() {
118			return name;
119		}
120
121		public void setName(String user) {
122			this.name = user;
123		}
124
125		public void setJid(Jid jid) {
126			this.jid = jid;
127		}
128
129		public Jid getJid() {
130			return this.jid;
131		}
132
133		public Role getRole() {
134			return this.role;
135		}
136
137		public void setRole(String role) {
138			role = role.toLowerCase();
139			switch (role) {
140				case "moderator":
141					this.role = Role.MODERATOR;
142					break;
143				case "participant":
144					this.role = Role.PARTICIPANT;
145					break;
146				case "visitor":
147					this.role = Role.VISITOR;
148					break;
149				default:
150					this.role = Role.NONE;
151					break;
152			}
153		}
154
155		public Affiliation getAffiliation() {
156			return this.affiliation;
157		}
158
159		public void setAffiliation(String affiliation) {
160			affiliation = affiliation.toLowerCase();
161			switch (affiliation) {
162				case "admin":
163					this.affiliation = Affiliation.ADMIN;
164					break;
165				case "owner":
166					this.affiliation = Affiliation.OWNER;
167					break;
168				case "member":
169					this.affiliation = Affiliation.MEMBER;
170					break;
171				case "outcast":
172					this.affiliation = Affiliation.OUTCAST;
173					break;
174				default:
175					this.affiliation = Affiliation.NONE;
176			}
177		}
178
179		public void setPgpKeyId(long id) {
180			this.pgpKeyId = id;
181		}
182
183		public long getPgpKeyId() {
184			return this.pgpKeyId;
185		}
186
187		public Contact getContact() {
188			return account.getRoster().getContactFromRoster(getJid());
189		}
190	}
191
192	private Account account;
193	private List<User> users = new CopyOnWriteArrayList<>();
194	private List<String> features = new ArrayList<>();
195	private Conversation conversation;
196	private boolean isOnline = false;
197	private int error = ERROR_UNKNOWN;
198	private OnRenameListener onRenameListener = null;
199	private OnJoinListener onJoinListener = null;
200	private User self = new User();
201	private String subject = null;
202	private String password = null;
203	private boolean mNickChangingInProgress = false;
204
205	public MucOptions(Conversation conversation) {
206		this.account = conversation.getAccount();
207		this.conversation = conversation;
208	}
209
210	public void updateFeatures(ArrayList<String> features) {
211		this.features.clear();
212		this.features.addAll(features);
213	}
214
215	public boolean hasFeature(String feature) {
216		return this.features.contains(feature);
217	}
218
219	public boolean canInvite() {
220		 return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN);
221	}
222
223	public boolean membersOnly() {
224		return hasFeature("muc_membersonly");
225	}
226
227	public void deleteUser(String name) {
228		for (int i = 0; i < users.size(); ++i) {
229			if (users.get(i).getName().equals(name)) {
230				users.remove(i);
231				return;
232			}
233		}
234	}
235
236	public void addUser(User user) {
237		for (int i = 0; i < users.size(); ++i) {
238			if (users.get(i).getName().equals(user.getName())) {
239				users.set(i, user);
240				return;
241			}
242		}
243		users.add(user);
244	}
245
246	public void processPacket(PresencePacket packet, PgpEngine pgp) {
247		Log.d(Config.LOGTAG, packet.toString());
248		final Jid from = packet.getFrom();
249		if (!from.isBareJid()) {
250			final String name = from.getResourcepart();
251			final String type = packet.getAttribute("type");
252			final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
253			final List<String> codes = getStatusCodes(x);
254			if (type == null) {
255				User user = new User();
256				if (x != null) {
257					Element item = x.findChild("item");
258					if (item != null) {
259						user.setName(name);
260						user.setAffiliation(item.getAttribute("affiliation"));
261						user.setRole(item.getAttribute("role"));
262						user.setJid(item.getAttributeAsJid("jid"));
263						if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) {
264							this.isOnline = true;
265							this.error = ERROR_NO_ERROR;
266							self = user;
267							if (mNickChangingInProgress) {
268								onRenameListener.onSuccess();
269								mNickChangingInProgress = false;
270							} else if (this.onJoinListener != null) {
271								this.onJoinListener.onSuccess();
272								this.onJoinListener = null;
273							}
274						} else {
275							addUser(user);
276						}
277						if (pgp != null) {
278							Element signed = packet.findChild("x", "jabber:x:signed");
279							if (signed != null) {
280								Element status = packet.findChild("status");
281								String msg;
282								if (status != null) {
283									msg = status.getContent();
284								} else {
285									msg = "";
286								}
287								user.setPgpKeyId(pgp.fetchKeyId(account, msg,
288										signed.getContent()));
289							}
290						}
291					}
292				}
293			} else if (type.equals("unavailable")) {
294				if (codes.contains(STATUS_CODE_SELF_PRESENCE) ||
295						packet.getFrom().equals(this.conversation.getJid())) {
296					if (codes.contains(STATUS_CODE_CHANGED_NICK)) {
297						this.mNickChangingInProgress = true;
298					} else if (codes.contains(STATUS_CODE_KICKED)) {
299						setError(KICKED_FROM_ROOM);
300					} else if (codes.contains(STATUS_CODE_BANNED)) {
301						setError(ERROR_BANNED);
302					} else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) {
303						setError(ERROR_MEMBERS_ONLY);
304					} else {
305						setError(ERROR_UNKNOWN);
306					}
307				} else {
308					deleteUser(name);
309				}
310			} else if (type.equals("error")) {
311				Element error = packet.findChild("error");
312				if (error != null && error.hasChild("conflict")) {
313					if (isOnline) {
314						if (onRenameListener != null) {
315							onRenameListener.onFailure();
316						}
317					} else {
318						setError(ERROR_NICK_IN_USE);
319					}
320				} else if (error != null && error.hasChild("not-authorized")) {
321					setError(ERROR_PASSWORD_REQUIRED);
322				} else if (error != null && error.hasChild("forbidden")) {
323					setError(ERROR_BANNED);
324				} else if (error != null && error.hasChild("registration-required")) {
325					setError(ERROR_MEMBERS_ONLY);
326				} else {
327					setError(ERROR_UNKNOWN);
328				}
329			}
330		}
331	}
332
333	private void setError(int error) {
334		this.isOnline = false;
335		this.error = error;
336		if (onJoinListener != null) {
337			onJoinListener.onFailure();
338			onJoinListener = null;
339		}
340	}
341
342	private List<String> getStatusCodes(Element x) {
343		List<String> codes = new ArrayList<>();
344		if (x != null) {
345			for (Element child : x.getChildren()) {
346				if (child.getName().equals("status")) {
347					String code = child.getAttribute("code");
348					if (code != null) {
349						codes.add(code);
350					}
351				}
352			}
353		}
354		return codes;
355	}
356
357	public List<User> getUsers() {
358		return this.users;
359	}
360
361	public String getProposedNick() {
362		if (conversation.getBookmark() != null
363				&& conversation.getBookmark().getNick() != null
364				&& !conversation.getBookmark().getNick().isEmpty()) {
365			return conversation.getBookmark().getNick();
366		} else if (!conversation.getJid().isBareJid()) {
367			return conversation.getJid().getResourcepart();
368		} else {
369			return account.getUsername();
370		}
371	}
372
373	public String getActualNick() {
374		if (this.self.getName() != null) {
375			return this.self.getName();
376		} else {
377			return this.getProposedNick();
378		}
379	}
380
381	public boolean online() {
382		return this.isOnline;
383	}
384
385	public int getError() {
386		return this.error;
387	}
388
389	public void setOnRenameListener(OnRenameListener listener) {
390		this.onRenameListener = listener;
391	}
392
393	public void setOnJoinListener(OnJoinListener listener) {
394		this.onJoinListener = listener;
395	}
396
397	public void setOffline() {
398		this.users.clear();
399		this.error = 0;
400		this.isOnline = false;
401	}
402
403	public User getSelf() {
404		return self;
405	}
406
407	public void setSubject(String content) {
408		this.subject = content;
409	}
410
411	public String getSubject() {
412		return this.subject;
413	}
414
415	public String createNameFromParticipants() {
416		if (users.size() >= 2) {
417			List<String> names = new ArrayList<String>();
418			for (User user : users) {
419				Contact contact = user.getContact();
420				if (contact != null && !contact.getDisplayName().isEmpty()) {
421					names.add(contact.getDisplayName().split("\\s+")[0]);
422				} else {
423					names.add(user.getName());
424				}
425			}
426			StringBuilder builder = new StringBuilder();
427			for (int i = 0; i < names.size(); ++i) {
428				builder.append(names.get(i));
429				if (i != names.size() - 1) {
430					builder.append(", ");
431				}
432			}
433			return builder.toString();
434		} else {
435			return null;
436		}
437	}
438
439	public long[] getPgpKeyIds() {
440		List<Long> ids = new ArrayList<>();
441		for (User user : getUsers()) {
442			if (user.getPgpKeyId() != 0) {
443				ids.add(user.getPgpKeyId());
444			}
445		}
446		long[] primitivLongArray = new long[ids.size()];
447		for (int i = 0; i < ids.size(); ++i) {
448			primitivLongArray[i] = ids.get(i);
449		}
450		return primitivLongArray;
451	}
452
453	public boolean pgpKeysInUse() {
454		for (User user : getUsers()) {
455			if (user.getPgpKeyId() != 0) {
456				return true;
457			}
458		}
459		return false;
460	}
461
462	public boolean everybodyHasKeys() {
463		for (User user : getUsers()) {
464			if (user.getPgpKeyId() == 0) {
465				return false;
466			}
467		}
468		return true;
469	}
470
471	public Jid createJoinJid(String nick) {
472		try {
473			return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
474		} catch (final InvalidJidException e) {
475			return null;
476		}
477	}
478
479	public Jid getTrueCounterpart(String counterpart) {
480		for (User user : this.getUsers()) {
481			if (user.getName().equals(counterpart)) {
482				return user.getJid();
483			}
484		}
485		return null;
486	}
487
488	public String getPassword() {
489		this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
490		if (this.password == null && conversation.getBookmark() != null
491				&& conversation.getBookmark().getPassword() != null) {
492			return conversation.getBookmark().getPassword();
493		} else {
494			return this.password;
495		}
496	}
497
498	public void setPassword(String password) {
499		if (conversation.getBookmark() != null) {
500			conversation.getBookmark().setPassword(password);
501		} else {
502			this.password = password;
503		}
504		conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
505	}
506
507	public Conversation getConversation() {
508		return this.conversation;
509	}
510}