ConferenceDetailsActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.app.PendingIntent;
  4import android.content.Context;
  5import android.content.Intent;
  6import android.content.IntentSender.SendIntentException;
  7import android.content.res.Resources;
  8import android.databinding.DataBindingUtil;
  9import android.graphics.Bitmap;
 10import android.graphics.drawable.BitmapDrawable;
 11import android.graphics.drawable.Drawable;
 12import android.os.AsyncTask;
 13import android.os.Bundle;
 14import android.support.v7.app.AlertDialog;
 15import android.support.v7.widget.Toolbar;
 16import android.util.Log;
 17import android.view.ContextMenu;
 18import android.view.LayoutInflater;
 19import android.view.Menu;
 20import android.view.MenuItem;
 21import android.view.View;
 22import android.view.View.OnClickListener;
 23import android.widget.ImageView;
 24import android.widget.Toast;
 25
 26import org.openintents.openpgp.util.OpenPgpUtils;
 27
 28import java.lang.ref.WeakReference;
 29import java.util.ArrayList;
 30import java.util.Collections;
 31import java.util.concurrent.RejectedExecutionException;
 32import java.util.concurrent.atomic.AtomicInteger;
 33
 34import eu.siacs.conversations.Config;
 35import eu.siacs.conversations.R;
 36import eu.siacs.conversations.crypto.PgpEngine;
 37import eu.siacs.conversations.databinding.ActivityMucDetailsBinding;
 38import eu.siacs.conversations.databinding.ContactBinding;
 39import eu.siacs.conversations.entities.Account;
 40import eu.siacs.conversations.entities.Bookmark;
 41import eu.siacs.conversations.entities.Contact;
 42import eu.siacs.conversations.entities.Conversation;
 43import eu.siacs.conversations.entities.MucOptions;
 44import eu.siacs.conversations.entities.MucOptions.User;
 45import eu.siacs.conversations.services.XmppConnectionService;
 46import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
 47import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate;
 48import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 49import eu.siacs.conversations.utils.UIHelper;
 50import eu.siacs.conversations.utils.XmppUri;
 51import rocks.xmpp.addr.Jid;
 52
 53public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed {
 54	public static final String ACTION_VIEW_MUC = "view_muc";
 55
 56	private static final float INACTIVE_ALPHA = 0.4684f; //compromise between dark and light theme
 57
 58	private Conversation mConversation;
 59	private OnClickListener inviteListener = new OnClickListener() {
 60
 61		@Override
 62		public void onClick(View v) {
 63			inviteToConversation(mConversation);
 64		}
 65	};
 66	private ActivityMucDetailsBinding binding;
 67	private String uuid = null;
 68	private User mSelectedUser = null;
 69
 70	private boolean mAdvancedMode = false;
 71
 72	private UiCallback<Conversation> renameCallback = new UiCallback<Conversation>() {
 73		@Override
 74		public void success(Conversation object) {
 75			runOnUiThread(() -> {
 76				Toast.makeText(ConferenceDetailsActivity.this, getString(R.string.your_nick_has_been_changed), Toast.LENGTH_SHORT).show();
 77				updateView();
 78			});
 79
 80		}
 81
 82		@Override
 83		public void error(final int errorCode, Conversation object) {
 84			runOnUiThread(() -> Toast.makeText(ConferenceDetailsActivity.this, getString(errorCode), Toast.LENGTH_SHORT).show());
 85		}
 86
 87		@Override
 88		public void userInputRequried(PendingIntent pi, Conversation object) {
 89
 90		}
 91	};
 92
 93	private OnClickListener mNotifyStatusClickListener = new OnClickListener() {
 94		@Override
 95		public void onClick(View v) {
 96			AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this);
 97			builder.setTitle(R.string.pref_notification_settings);
 98			String[] choices = {
 99					getString(R.string.notify_on_all_messages),
100					getString(R.string.notify_only_when_highlighted),
101					getString(R.string.notify_never)
102			};
103			final AtomicInteger choice;
104			if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0) == Long.MAX_VALUE) {
105				choice = new AtomicInteger(2);
106			} else {
107				choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : 1);
108			}
109			builder.setSingleChoiceItems(choices, choice.get(), (dialog, which) -> choice.set(which));
110			builder.setNegativeButton(R.string.cancel, null);
111			builder.setPositiveButton(R.string.ok, (dialog, which) -> {
112				if (choice.get() == 2) {
113					mConversation.setMutedTill(Long.MAX_VALUE);
114				} else {
115					mConversation.setMutedTill(0);
116					mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY, String.valueOf(choice.get() == 0));
117				}
118				xmppConnectionService.updateConversation(mConversation);
119				updateView();
120			});
121			builder.create().show();
122		}
123	};
124
125	private OnClickListener mChangeConferenceSettings = new OnClickListener() {
126		@Override
127		public void onClick(View v) {
128			final MucOptions mucOptions = mConversation.getMucOptions();
129			AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this);
130			builder.setTitle(R.string.conference_options);
131			final String[] options;
132			final boolean[] values;
133			if (mAdvancedMode) {
134				options = new String[]{
135						getString(R.string.members_only),
136						getString(R.string.moderated),
137						getString(R.string.non_anonymous)
138				};
139				values = new boolean[]{
140						mucOptions.membersOnly(),
141						mucOptions.moderated(),
142						mucOptions.nonanonymous()
143				};
144			} else {
145				options = new String[]{
146						getString(R.string.members_only),
147						getString(R.string.non_anonymous)
148				};
149				values = new boolean[]{
150						mucOptions.membersOnly(),
151						mucOptions.nonanonymous()
152				};
153			}
154			builder.setMultiChoiceItems(options, values, (dialog, which, isChecked) -> values[which] = isChecked);
155			builder.setNegativeButton(R.string.cancel, null);
156			builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
157				if (!mucOptions.membersOnly() && values[0]) {
158					xmppConnectionService.changeAffiliationsInConference(mConversation,
159							MucOptions.Affiliation.NONE,
160							MucOptions.Affiliation.MEMBER);
161				}
162				Bundle options1 = new Bundle();
163				options1.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0");
164				if (values.length == 2) {
165					options1.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators");
166				} else if (values.length == 3) {
167					options1.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0");
168					options1.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators");
169				}
170				options1.putString("muc#roomconfig_persistentroom", "1");
171				final boolean whois = values.length == 2 ? values[1] : values[2];
172				if (values[0] == whois) {
173					options1.putString("muc#roomconfig_publicroom",whois ? "0" : "1");
174				}
175				xmppConnectionService.pushConferenceConfiguration(mConversation,
176						options1,
177						ConferenceDetailsActivity.this);
178			});
179			builder.create().show();
180		}
181	};
182	private OnValueEdited onSubjectEdited = new OnValueEdited() {
183
184		@Override
185		public String onValueEdited(String value) {
186			xmppConnectionService.pushSubjectToConference(mConversation, value.trim().isEmpty() ? null : value.trim());
187			return null;
188		}
189	};
190
191	public static boolean cancelPotentialWork(User user, ImageView imageView) {
192		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
193
194		if (bitmapWorkerTask != null) {
195			final User old = bitmapWorkerTask.o;
196			if (old == null || user != old) {
197				bitmapWorkerTask.cancel(true);
198			} else {
199				return false;
200			}
201		}
202		return true;
203	}
204
205	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
206		if (imageView != null) {
207			final Drawable drawable = imageView.getDrawable();
208			if (drawable instanceof AsyncDrawable) {
209				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
210				return asyncDrawable.getBitmapWorkerTask();
211			}
212		}
213		return null;
214	}
215
216	@Override
217	public void onConversationUpdate() {
218		refreshUi();
219	}
220
221	@Override
222	public void onMucRosterUpdate() {
223		refreshUi();
224	}
225
226	@Override
227	protected void refreshUiReal() {
228		updateView();
229	}
230
231	@Override
232	protected void onCreate(Bundle savedInstanceState) {
233		super.onCreate(savedInstanceState);
234		this.binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_details);
235		this.binding.mucMoreDetails.setVisibility(View.GONE);
236		this.binding.changeConferenceButton.setOnClickListener(this.mChangeConferenceSettings);
237		this.binding.invite.setOnClickListener(inviteListener);
238		setSupportActionBar((Toolbar) binding.toolbar);
239		configureActionBar(getSupportActionBar());
240		this.binding.editNickButton.setOnClickListener(v -> quickEdit(mConversation.getMucOptions().getActualNick(),
241				0,
242				value -> {
243					if (xmppConnectionService.renameInMuc(mConversation, value, renameCallback)) {
244						return null;
245					} else {
246						return getString(R.string.invalid_username);
247					}
248				}));
249		this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false);
250		this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
251		this.binding.notificationStatusButton.setOnClickListener(this.mNotifyStatusClickListener);
252		this.binding.yourPhoto.setOnClickListener(v -> {
253			final MucOptions mucOptions = mConversation.getMucOptions();
254			if (!mucOptions.hasVCards()) {
255				Toast.makeText(this,R.string.host_does_not_support_group_chat_avatars, Toast.LENGTH_SHORT).show();
256				return;
257			}
258			if (!mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
259				Toast.makeText(this,R.string.only_the_owner_can_change_group_chat_avatar, Toast.LENGTH_SHORT).show();
260				return;
261			}
262			final Intent intent = new Intent(this, PublishGroupChatProfilePictureActivity.class);
263			intent.putExtra("uuid",mConversation.getUuid());
264			startActivity(intent);
265		});
266	}
267
268	@Override
269	protected void onStart() {
270		super.onStart();
271		final int theme = findTheme();
272		if (this.mTheme != theme) {
273			recreate();
274		}
275	}
276
277	@Override
278	public boolean onOptionsItemSelected(MenuItem menuItem) {
279		if (MenuDoubleTabUtil.shouldIgnoreTap()) {
280			return false;
281		}
282		switch (menuItem.getItemId()) {
283			case android.R.id.home:
284				finish();
285				break;
286			case R.id.action_share_http:
287				shareLink(true);
288				break;
289			case R.id.action_share_uri:
290				shareLink(false);
291				break;
292			case R.id.action_save_as_bookmark:
293				saveAsBookmark();
294				break;
295			case R.id.action_delete_bookmark:
296				deleteBookmark();
297				break;
298			case R.id.action_advanced_mode:
299				this.mAdvancedMode = !menuItem.isChecked();
300				menuItem.setChecked(this.mAdvancedMode);
301				getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).apply();
302				final boolean online = mConversation != null && mConversation.getMucOptions().online();
303				this.binding.mucInfoMore.setVisibility(this.mAdvancedMode && online ? View.VISIBLE : View.GONE);
304				invalidateOptionsMenu();
305				updateView();
306				break;
307		}
308		return super.onOptionsItemSelected(menuItem);
309	}
310
311	@Override
312	protected String getShareableUri(boolean http) {
313		if (mConversation != null) {
314			if (http) {
315				return "https://conversations.im/j/" + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toEscapedString());
316			} else {
317				return "xmpp:" + mConversation.getJid().asBareJid() + "?join";
318			}
319		} else {
320			return null;
321		}
322	}
323
324	@Override
325	public boolean onPrepareOptionsMenu(Menu menu) {
326		MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
327		MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
328		MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
329		menuItemAdvancedMode.setChecked(mAdvancedMode);
330		if (mConversation == null) {
331			return true;
332		}
333		if (mConversation.getBookmark() != null) {
334			menuItemSaveBookmark.setVisible(false);
335			menuItemDeleteBookmark.setVisible(true);
336		} else {
337			menuItemDeleteBookmark.setVisible(false);
338			menuItemSaveBookmark.setVisible(true);
339		}
340		return true;
341	}
342
343	@Override
344	public boolean onCreateOptionsMenu(Menu menu) {
345		getMenuInflater().inflate(R.menu.muc_details, menu);
346		return super.onCreateOptionsMenu(menu);
347	}
348
349	@Override
350	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
351		Object tag = v.getTag();
352		if (tag instanceof User) {
353			getMenuInflater().inflate(R.menu.muc_details_context, menu);
354			final User user = (User) tag;
355			final User self = mConversation.getMucOptions().getSelf();
356			this.mSelectedUser = user;
357			String name;
358			final Contact contact = user.getContact();
359			if (contact != null && contact.showInRoster()) {
360				name = contact.getDisplayName();
361			} else if (user.getRealJid() != null) {
362				name = user.getRealJid().asBareJid().toString();
363			} else {
364				name = user.getName();
365			}
366			menu.setHeaderTitle(name);
367			if (user.getRealJid() != null) {
368				MenuItem showContactDetails = menu.findItem(R.id.action_contact_details);
369				MenuItem startConversation = menu.findItem(R.id.start_conversation);
370				MenuItem giveMembership = menu.findItem(R.id.give_membership);
371				MenuItem removeMembership = menu.findItem(R.id.remove_membership);
372				MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges);
373				MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges);
374				MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room);
375				MenuItem banFromConference = menu.findItem(R.id.ban_from_conference);
376				MenuItem invite = menu.findItem(R.id.invite);
377				startConversation.setVisible(true);
378				if (contact != null && contact.showInRoster()) {
379					showContactDetails.setVisible(!contact.isSelf());
380				}
381				if (user.getRole() == MucOptions.Role.NONE) {
382					invite.setVisible(true);
383				}
384				if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) &&
385						self.getAffiliation().outranks(user.getAffiliation())) {
386					if (mAdvancedMode) {
387						if (user.getAffiliation() == MucOptions.Affiliation.NONE) {
388							giveMembership.setVisible(true);
389						} else {
390							removeMembership.setVisible(true);
391						}
392						if (!Config.DISABLE_BAN) {
393							banFromConference.setVisible(true);
394						}
395					} else {
396						if (!Config.DISABLE_BAN || mConversation.getMucOptions().membersOnly()) {
397							removeFromRoom.setVisible(true);
398						}
399					}
400					if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) {
401						giveAdminPrivileges.setVisible(true);
402					} else {
403						removeAdminPrivileges.setVisible(true);
404					}
405				}
406			} else {
407				MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message);
408				sendPrivateMessage.setVisible(user.getRole().ranks(MucOptions.Role.VISITOR));
409			}
410
411		}
412		super.onCreateContextMenu(menu, v, menuInfo);
413	}
414
415	@Override
416	public boolean onContextItemSelected(MenuItem item) {
417		Jid jid = mSelectedUser.getRealJid();
418		switch (item.getItemId()) {
419			case R.id.action_contact_details:
420				Contact contact = mSelectedUser.getContact();
421				if (contact != null) {
422					switchToContactDetails(contact);
423				}
424				return true;
425			case R.id.start_conversation:
426				startConversation(mSelectedUser);
427				return true;
428			case R.id.give_admin_privileges:
429				xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.ADMIN, this);
430				return true;
431			case R.id.give_membership:
432				xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER, this);
433				return true;
434			case R.id.remove_membership:
435				xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.NONE, this);
436				return true;
437			case R.id.remove_admin_privileges:
438				xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER, this);
439				return true;
440			case R.id.remove_from_room:
441				removeFromRoom(mSelectedUser);
442				return true;
443			case R.id.ban_from_conference:
444				xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.OUTCAST, this);
445				if (mSelectedUser.getRole() != MucOptions.Role.NONE) {
446					xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, this);
447				}
448				return true;
449			case R.id.send_private_message:
450				if (mConversation.getMucOptions().allowPm()) {
451					privateMsgInMuc(mConversation, mSelectedUser.getName());
452				} else {
453					Toast.makeText(this, R.string.private_messages_are_disabled, Toast.LENGTH_SHORT).show();
454				}
455				return true;
456			case R.id.invite:
457				xmppConnectionService.directInvite(mConversation, jid);
458				return true;
459			default:
460				return super.onContextItemSelected(item);
461		}
462	}
463
464	private void removeFromRoom(final User user) {
465		if (mConversation.getMucOptions().membersOnly()) {
466			xmppConnectionService.changeAffiliationInConference(mConversation, user.getRealJid(), MucOptions.Affiliation.NONE, this);
467			if (user.getRole() != MucOptions.Role.NONE) {
468				xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this);
469			}
470		} else {
471			AlertDialog.Builder builder = new AlertDialog.Builder(this);
472			builder.setTitle(R.string.ban_from_conference);
473			builder.setMessage(getString(R.string.removing_from_public_conference, user.getName()));
474			builder.setNegativeButton(R.string.cancel, null);
475			builder.setPositiveButton(R.string.ban_now, (dialog, which) -> {
476				xmppConnectionService.changeAffiliationInConference(mConversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, ConferenceDetailsActivity.this);
477				if (user.getRole() != MucOptions.Role.NONE) {
478					xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this);
479				}
480			});
481			builder.create().show();
482		}
483	}
484
485	protected void startConversation(User user) {
486		if (user.getRealJid() != null) {
487			Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(), user.getRealJid().asBareJid(), false, true);
488			switchToConversation(conversation);
489		}
490	}
491
492	protected void saveAsBookmark() {
493		xmppConnectionService.saveConversationAsBookmark(mConversation,
494				mConversation.getMucOptions().getSubject());
495	}
496
497	protected void deleteBookmark() {
498		Account account = mConversation.getAccount();
499		Bookmark bookmark = mConversation.getBookmark();
500		account.getBookmarks().remove(bookmark);
501		bookmark.setConversation(null);
502		xmppConnectionService.pushBookmarks(account);
503		updateView();
504	}
505
506	@Override
507	void onBackendConnected() {
508		if (mPendingConferenceInvite != null) {
509			mPendingConferenceInvite.execute(this);
510			mPendingConferenceInvite = null;
511		}
512		if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
513			this.uuid = getIntent().getExtras().getString("uuid");
514		}
515		if (uuid != null) {
516			this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
517			if (this.mConversation != null) {
518				updateView();
519			}
520		}
521	}
522
523	private void updateView() {
524		invalidateOptionsMenu();
525		final MucOptions mucOptions = mConversation.getMucOptions();
526		final User self = mucOptions.getSelf();
527		String account;
528		if (Config.DOMAIN_LOCK != null) {
529			account = mConversation.getAccount().getJid().getLocal();
530		} else {
531			account = mConversation.getAccount().getJid().asBareJid().toString();
532		}
533		this.binding.detailsAccount.setText(getString(R.string.using_account, account));
534		this.binding.yourPhoto.setImageBitmap(avatarService().get(mConversation, getPixel(72)));
535		this.binding.mucTitle.setText(mucOptions.getName());
536		this.binding.mucSubject.setText(mucOptions.getSubject());
537		this.binding.mucYourNick.setText(mucOptions.getActualNick());
538		if (mucOptions.online()) {
539			this.binding.mucMoreDetails.setVisibility(View.VISIBLE);
540			this.binding.mucSettings.setVisibility(View.VISIBLE);
541			this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
542			this.binding.mucRole.setVisibility(View.VISIBLE);
543			this.binding.mucRole.setText(getStatus(self));
544			if (mucOptions.membersOnly()) {
545				this.binding.mucConferenceType.setText(R.string.private_conference);
546			} else {
547				this.binding.mucConferenceType.setText(R.string.public_conference);
548			}
549			if (mucOptions.mamSupport()) {
550				this.binding.mucInfoMam.setText(R.string.server_info_available);
551			} else {
552				this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
553			}
554			if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
555				this.binding.changeConferenceButton.setVisibility(View.VISIBLE);
556			} else {
557				this.binding.changeConferenceButton.setVisibility(View.GONE);
558			}
559		} else {
560			this.binding.mucMoreDetails.setVisibility(View.GONE);
561			this.binding.mucInfoMore.setVisibility(View.GONE);
562			this.binding.mucSettings.setVisibility(View.GONE);
563		}
564
565		int ic_notifications = getThemeResource(R.attr.icon_notifications, R.drawable.ic_notifications_black_24dp);
566		int ic_notifications_off = getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
567		int ic_notifications_paused = getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
568		int ic_notifications_none = getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
569
570		long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
571		if (mutedTill == Long.MAX_VALUE) {
572			this.binding.notificationStatusText.setText(R.string.notify_never);
573			this.binding.notificationStatusButton.setImageResource(ic_notifications_off);
574		} else if (System.currentTimeMillis() < mutedTill) {
575			this.binding.notificationStatusText.setText(R.string.notify_paused);
576			this.binding.notificationStatusButton.setImageResource(ic_notifications_paused);
577		} else if (mConversation.alwaysNotify()) {
578			this.binding.notificationStatusText.setText(R.string.notify_on_all_messages);
579			this.binding.notificationStatusButton.setImageResource(ic_notifications);
580		} else {
581			this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted);
582			this.binding.notificationStatusButton.setImageResource(ic_notifications_none);
583		}
584
585		final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
586		this.binding.mucMembers.removeAllViews();
587		if (inflater == null) {
588			return;
589		}
590		final ArrayList<User> users = mucOptions.getUsers();
591		Collections.sort(users);
592		for (final User user : users) {
593			ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, this.binding.mucMembers, false);
594			this.setListItemBackgroundOnView(binding.getRoot());
595			binding.getRoot().setOnClickListener(view1 -> highlightInMuc(mConversation, user.getName()));
596			registerForContextMenu(binding.getRoot());
597			binding.getRoot().setTag(user);
598			if (mAdvancedMode && user.getPgpKeyId() != 0) {
599				binding.key.setVisibility(View.VISIBLE);
600				binding.key.setOnClickListener(v -> viewPgpKey(user));
601				binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
602			}
603			Contact contact = user.getContact();
604			String name = user.getName();
605			if (contact != null) {
606				binding.contactDisplayName.setText(contact.getDisplayName());
607				binding.contactJid.setText((name != null ? name + " \u2022 " : "") + getStatus(user));
608			} else {
609				binding.contactDisplayName.setText(name == null ? "" : name);
610				binding.contactJid.setText(getStatus(user));
611
612			}
613			loadAvatar(user, binding.contactPhoto);
614			if (user.getRole() == MucOptions.Role.NONE) {
615				binding.contactJid.setAlpha(INACTIVE_ALPHA);
616				binding.key.setAlpha(INACTIVE_ALPHA);
617				binding.contactDisplayName.setAlpha(INACTIVE_ALPHA);
618				binding.contactPhoto.setAlpha(INACTIVE_ALPHA);
619			}
620			this.binding.mucMembers.addView(binding.getRoot());
621			if (mConversation.getMucOptions().canInvite()) {
622				this.binding.invite.setVisibility(View.VISIBLE);
623			} else {
624				this.binding.invite.setVisibility(View.GONE);
625			}
626		}
627	}
628
629	private String getStatus(User user) {
630		if (mAdvancedMode) {
631			return getString(user.getAffiliation().getResId()) +
632					" (" + getString(user.getRole().getResId()) + ')';
633		} else {
634			return getString(user.getAffiliation().getResId());
635		}
636	}
637
638	private void viewPgpKey(User user) {
639		PgpEngine pgp = xmppConnectionService.getPgpEngine();
640		if (pgp != null) {
641			PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId());
642			if (intent != null) {
643				try {
644					startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
645				} catch (SendIntentException ignored) {
646
647				}
648			}
649		}
650	}
651
652	@Override
653	public void onAffiliationChangedSuccessful(Jid jid) {
654		refreshUi();
655	}
656
657	@Override
658	public void onAffiliationChangeFailed(Jid jid, int resId) {
659		displayToast(getString(resId, jid.asBareJid().toString()));
660	}
661
662	@Override
663	public void onRoleChangedSuccessful(String nick) {
664
665	}
666
667	@Override
668	public void onRoleChangeFailed(String nick, int resId) {
669		displayToast(getString(resId, nick));
670	}
671
672	@Override
673	public void onPushSucceeded() {
674		displayToast(getString(R.string.modified_conference_options));
675	}
676
677	@Override
678	public void onPushFailed() {
679		displayToast(getString(R.string.could_not_modify_conference_options));
680	}
681
682	private void displayToast(final String msg) {
683		runOnUiThread(() -> Toast.makeText(ConferenceDetailsActivity.this, msg, Toast.LENGTH_SHORT).show());
684	}
685
686	public void loadAvatar(User user, ImageView imageView) {
687		if (cancelPotentialWork(user, imageView)) {
688			final Bitmap bm = avatarService().get(user, getPixel(48), true);
689			if (bm != null) {
690				cancelPotentialWork(user, imageView);
691				imageView.setImageBitmap(bm);
692				imageView.setBackgroundColor(0x00000000);
693			} else {
694				String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null;
695				imageView.setBackgroundColor(UIHelper.getColorForName(seed == null ? user.getName() : seed));
696				imageView.setImageDrawable(null);
697				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
698				final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), null, task);
699				imageView.setImageDrawable(asyncDrawable);
700				try {
701					task.execute(user);
702				} catch (final RejectedExecutionException ignored) {
703				}
704			}
705		}
706	}
707
708	static class AsyncDrawable extends BitmapDrawable {
709		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
710
711		AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
712			super(res, bitmap);
713			bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
714		}
715
716		BitmapWorkerTask getBitmapWorkerTask() {
717			return bitmapWorkerTaskReference.get();
718		}
719	}
720
721	class BitmapWorkerTask extends AsyncTask<User, Void, Bitmap> {
722		private final WeakReference<ImageView> imageViewReference;
723		private User o = null;
724
725		private BitmapWorkerTask(ImageView imageView) {
726			imageViewReference = new WeakReference<>(imageView);
727		}
728
729		@Override
730		protected Bitmap doInBackground(User... params) {
731			this.o = params[0];
732			if (imageViewReference.get() == null) {
733				return null;
734			}
735			return avatarService().get(this.o, getPixel(48), isCancelled());
736		}
737
738		@Override
739		protected void onPostExecute(Bitmap bitmap) {
740			if (bitmap != null && !isCancelled()) {
741				final ImageView imageView = imageViewReference.get();
742				if (imageView != null) {
743					imageView.setImageBitmap(bitmap);
744					imageView.setBackgroundColor(0x00000000);
745				}
746			}
747		}
748	}
749
750}