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