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