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