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.jid.setText(mConversation.getJid().asBareJid().toEscapedString());
535		this.binding.yourPhoto.setImageBitmap(avatarService().get(mConversation, getPixel(72)));
536		this.binding.mucTitle.setText(mucOptions.getName());
537		this.binding.mucSubject.setText(mucOptions.getSubject());
538		this.binding.mucYourNick.setText(mucOptions.getActualNick());
539		if (mucOptions.online()) {
540			this.binding.mucMoreDetails.setVisibility(View.VISIBLE);
541			this.binding.mucSettings.setVisibility(View.VISIBLE);
542			this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
543			this.binding.mucRole.setVisibility(View.VISIBLE);
544			this.binding.mucRole.setText(getStatus(self));
545			if (mucOptions.membersOnly()) {
546				this.binding.mucConferenceType.setText(R.string.private_conference);
547			} else {
548				this.binding.mucConferenceType.setText(R.string.public_conference);
549			}
550			if (mucOptions.mamSupport()) {
551				this.binding.mucInfoMam.setText(R.string.server_info_available);
552			} else {
553				this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
554			}
555			if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
556				this.binding.changeConferenceButton.setVisibility(View.VISIBLE);
557			} else {
558				this.binding.changeConferenceButton.setVisibility(View.INVISIBLE);
559			}
560		} else {
561			this.binding.mucMoreDetails.setVisibility(View.GONE);
562			this.binding.mucInfoMore.setVisibility(View.GONE);
563			this.binding.mucSettings.setVisibility(View.GONE);
564		}
565
566		int ic_notifications = getThemeResource(R.attr.icon_notifications, R.drawable.ic_notifications_black_24dp);
567		int ic_notifications_off = getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
568		int ic_notifications_paused = getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
569		int ic_notifications_none = getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
570
571		long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
572		if (mutedTill == Long.MAX_VALUE) {
573			this.binding.notificationStatusText.setText(R.string.notify_never);
574			this.binding.notificationStatusButton.setImageResource(ic_notifications_off);
575		} else if (System.currentTimeMillis() < mutedTill) {
576			this.binding.notificationStatusText.setText(R.string.notify_paused);
577			this.binding.notificationStatusButton.setImageResource(ic_notifications_paused);
578		} else if (mConversation.alwaysNotify()) {
579			this.binding.notificationStatusText.setText(R.string.notify_on_all_messages);
580			this.binding.notificationStatusButton.setImageResource(ic_notifications);
581		} else {
582			this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted);
583			this.binding.notificationStatusButton.setImageResource(ic_notifications_none);
584		}
585
586		final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
587		this.binding.mucMembers.removeAllViews();
588		if (inflater == null) {
589			return;
590		}
591		final ArrayList<User> users = mucOptions.getUsers();
592		Collections.sort(users);
593		for (final User user : users) {
594			ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, this.binding.mucMembers, false);
595			this.setListItemBackgroundOnView(binding.getRoot());
596			binding.getRoot().setOnClickListener(view1 -> highlightInMuc(mConversation, user.getName()));
597			registerForContextMenu(binding.getRoot());
598			binding.getRoot().setTag(user);
599			if (mAdvancedMode && user.getPgpKeyId() != 0) {
600				binding.key.setVisibility(View.VISIBLE);
601				binding.key.setOnClickListener(v -> viewPgpKey(user));
602				binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
603			}
604			Contact contact = user.getContact();
605			String name = user.getName();
606			if (contact != null) {
607				binding.contactDisplayName.setText(contact.getDisplayName());
608				binding.contactJid.setText((name != null ? name + " \u2022 " : "") + getStatus(user));
609			} else {
610				binding.contactDisplayName.setText(name == null ? "" : name);
611				binding.contactJid.setText(getStatus(user));
612
613			}
614			loadAvatar(user, binding.contactPhoto);
615			if (user.getRole() == MucOptions.Role.NONE) {
616				binding.contactJid.setAlpha(INACTIVE_ALPHA);
617				binding.key.setAlpha(INACTIVE_ALPHA);
618				binding.contactDisplayName.setAlpha(INACTIVE_ALPHA);
619				binding.contactPhoto.setAlpha(INACTIVE_ALPHA);
620			}
621			this.binding.mucMembers.addView(binding.getRoot());
622			if (mConversation.getMucOptions().canInvite()) {
623				this.binding.invite.setVisibility(View.VISIBLE);
624			} else {
625				this.binding.invite.setVisibility(View.GONE);
626			}
627		}
628	}
629
630	private String getStatus(User user) {
631		if (mAdvancedMode) {
632			return getString(user.getAffiliation().getResId()) +
633					" (" + getString(user.getRole().getResId()) + ')';
634		} else {
635			return getString(user.getAffiliation().getResId());
636		}
637	}
638
639	private void viewPgpKey(User user) {
640		PgpEngine pgp = xmppConnectionService.getPgpEngine();
641		if (pgp != null) {
642			PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId());
643			if (intent != null) {
644				try {
645					startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
646				} catch (SendIntentException ignored) {
647
648				}
649			}
650		}
651	}
652
653	@Override
654	public void onAffiliationChangedSuccessful(Jid jid) {
655		refreshUi();
656	}
657
658	@Override
659	public void onAffiliationChangeFailed(Jid jid, int resId) {
660		displayToast(getString(resId, jid.asBareJid().toString()));
661	}
662
663	@Override
664	public void onRoleChangedSuccessful(String nick) {
665
666	}
667
668	@Override
669	public void onRoleChangeFailed(String nick, int resId) {
670		displayToast(getString(resId, nick));
671	}
672
673	@Override
674	public void onPushSucceeded() {
675		displayToast(getString(R.string.modified_conference_options));
676	}
677
678	@Override
679	public void onPushFailed() {
680		displayToast(getString(R.string.could_not_modify_conference_options));
681	}
682
683	private void displayToast(final String msg) {
684		runOnUiThread(() -> Toast.makeText(ConferenceDetailsActivity.this, msg, Toast.LENGTH_SHORT).show());
685	}
686
687	public void loadAvatar(User user, ImageView imageView) {
688		if (cancelPotentialWork(user, imageView)) {
689			final Bitmap bm = avatarService().get(user, getPixel(48), true);
690			if (bm != null) {
691				cancelPotentialWork(user, imageView);
692				imageView.setImageBitmap(bm);
693				imageView.setBackgroundColor(0x00000000);
694			} else {
695				String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null;
696				imageView.setBackgroundColor(UIHelper.getColorForName(seed == null ? user.getName() : seed));
697				imageView.setImageDrawable(null);
698				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
699				final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), null, task);
700				imageView.setImageDrawable(asyncDrawable);
701				try {
702					task.execute(user);
703				} catch (final RejectedExecutionException ignored) {
704				}
705			}
706		}
707	}
708
709	static class AsyncDrawable extends BitmapDrawable {
710		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
711
712		AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
713			super(res, bitmap);
714			bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
715		}
716
717		BitmapWorkerTask getBitmapWorkerTask() {
718			return bitmapWorkerTaskReference.get();
719		}
720	}
721
722	class BitmapWorkerTask extends AsyncTask<User, Void, Bitmap> {
723		private final WeakReference<ImageView> imageViewReference;
724		private User o = null;
725
726		private BitmapWorkerTask(ImageView imageView) {
727			imageViewReference = new WeakReference<>(imageView);
728		}
729
730		@Override
731		protected Bitmap doInBackground(User... params) {
732			this.o = params[0];
733			if (imageViewReference.get() == null) {
734				return null;
735			}
736			return avatarService().get(this.o, getPixel(48), isCancelled());
737		}
738
739		@Override
740		protected void onPostExecute(Bitmap bitmap) {
741			if (bitmap != null && !isCancelled()) {
742				final ImageView imageView = imageViewReference.get();
743				if (imageView != null) {
744					imageView.setImageBitmap(bitmap);
745					imageView.setBackgroundColor(0x00000000);
746				}
747			}
748		}
749	}
750
751}