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