ContactDetailsActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.Manifest;
  4import android.content.ActivityNotFoundException;
  5import android.content.DialogInterface;
  6import android.content.Intent;
  7import android.content.SharedPreferences;
  8import android.content.pm.PackageManager;
  9import android.content.res.ColorStateList;
 10import android.net.Uri;
 11import android.os.Build;
 12import android.os.Bundle;
 13import android.preference.PreferenceManager;
 14import android.provider.ContactsContract.CommonDataKinds;
 15import android.provider.ContactsContract.Contacts;
 16import android.provider.ContactsContract.Intents;
 17import android.text.Spannable;
 18import android.text.SpannableString;
 19import android.text.style.RelativeSizeSpan;
 20import android.view.LayoutInflater;
 21import android.view.Menu;
 22import android.view.MenuItem;
 23import android.view.View;
 24import android.view.View.OnClickListener;
 25import android.widget.CompoundButton;
 26import android.widget.CompoundButton.OnCheckedChangeListener;
 27import android.widget.TextView;
 28import android.widget.Toast;
 29import androidx.annotation.NonNull;
 30import androidx.core.content.ContextCompat;
 31import androidx.core.view.ViewCompat;
 32import androidx.databinding.DataBindingUtil;
 33import com.google.android.material.color.MaterialColors;
 34import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 35import com.google.common.base.Joiner;
 36import com.google.common.collect.ImmutableList;
 37import com.google.common.collect.Iterables;
 38import com.google.common.primitives.Ints;
 39import eu.siacs.conversations.AppSettings;
 40import eu.siacs.conversations.Config;
 41import eu.siacs.conversations.R;
 42import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 43import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
 44import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
 45import eu.siacs.conversations.databinding.ActivityContactDetailsBinding;
 46import eu.siacs.conversations.entities.Account;
 47import eu.siacs.conversations.entities.Contact;
 48import eu.siacs.conversations.entities.ListItem;
 49import eu.siacs.conversations.services.AbstractQuickConversationsService;
 50import eu.siacs.conversations.services.QuickConversationsService;
 51import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
 52import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
 53import eu.siacs.conversations.ui.adapter.MediaAdapter;
 54import eu.siacs.conversations.ui.interfaces.OnMediaLoaded;
 55import eu.siacs.conversations.ui.util.Attachment;
 56import eu.siacs.conversations.ui.util.AvatarWorkerTask;
 57import eu.siacs.conversations.ui.util.GridManager;
 58import eu.siacs.conversations.ui.util.JidDialog;
 59import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 60import eu.siacs.conversations.utils.AccountUtils;
 61import eu.siacs.conversations.utils.Compatibility;
 62import eu.siacs.conversations.utils.Emoticons;
 63import eu.siacs.conversations.utils.IrregularUnicodeDetector;
 64import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
 65import eu.siacs.conversations.utils.UIHelper;
 66import eu.siacs.conversations.utils.XEP0392Helper;
 67import eu.siacs.conversations.utils.XmppUri;
 68import eu.siacs.conversations.xml.Namespace;
 69import eu.siacs.conversations.xmpp.Jid;
 70import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 71import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 72import eu.siacs.conversations.xmpp.XmppConnection;
 73import im.conversations.android.xmpp.model.stanza.Presence;
 74import java.util.Collection;
 75import java.util.Collections;
 76import java.util.List;
 77import org.openintents.openpgp.util.OpenPgpUtils;
 78
 79public class ContactDetailsActivity extends OmemoActivity
 80        implements OnAccountUpdate,
 81                OnRosterUpdate,
 82                OnUpdateBlocklist,
 83                OnKeyStatusUpdated,
 84                OnMediaLoaded {
 85    public static final String ACTION_VIEW_CONTACT = "view_contact";
 86    private final int REQUEST_SYNC_CONTACTS = 0x28cf;
 87    ActivityContactDetailsBinding binding;
 88    private MediaAdapter mMediaAdapter;
 89
 90    private Contact contact;
 91    private final DialogInterface.OnClickListener removeFromRoster =
 92            new DialogInterface.OnClickListener() {
 93
 94                @Override
 95                public void onClick(DialogInterface dialog, int which) {
 96                    xmppConnectionService.deleteContactOnServer(contact);
 97                }
 98            };
 99    private final OnCheckedChangeListener mOnSendCheckedChange =
100            new OnCheckedChangeListener() {
101
102                @Override
103                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
104                    if (isChecked) {
105                        if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
106                            xmppConnectionService.stopPresenceUpdatesTo(contact);
107                        } else {
108                            contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
109                        }
110                    } else {
111                        contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
112                        xmppConnectionService.sendPresencePacket(
113                                contact.getAccount(),
114                                xmppConnectionService
115                                        .getPresenceGenerator()
116                                        .stopPresenceUpdatesTo(contact));
117                    }
118                }
119            };
120    private final OnCheckedChangeListener mOnReceiveCheckedChange =
121            new OnCheckedChangeListener() {
122
123                @Override
124                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
125                    if (isChecked) {
126                        xmppConnectionService.sendPresencePacket(
127                                contact.getAccount(),
128                                xmppConnectionService
129                                        .getPresenceGenerator()
130                                        .requestPresenceUpdatesFrom(contact));
131                    } else {
132                        xmppConnectionService.sendPresencePacket(
133                                contact.getAccount(),
134                                xmppConnectionService
135                                        .getPresenceGenerator()
136                                        .stopPresenceUpdatesFrom(contact));
137                    }
138                }
139            };
140    private Jid accountJid;
141    private Jid contactJid;
142    private boolean showDynamicTags = false;
143    private boolean showLastSeen = false;
144    private boolean showInactiveOmemo = false;
145    private String messageFingerprint;
146
147    private void checkContactPermissionAndShowAddDialog() {
148        if (hasContactsPermission()) {
149            showAddToPhoneBookDialog();
150        } else if (QuickConversationsService.isContactListIntegration(this)
151                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
152            requestPermissions(
153                    new String[] {Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
154        }
155    }
156
157    private boolean hasContactsPermission() {
158        if (QuickConversationsService.isContactListIntegration(this)
159                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
160            return checkSelfPermission(Manifest.permission.READ_CONTACTS)
161                    == PackageManager.PERMISSION_GRANTED;
162        } else {
163            return true;
164        }
165    }
166
167    private void showAddToPhoneBookDialog() {
168        final Jid jid = contact.getJid();
169        final boolean quicksyContact =
170                AbstractQuickConversationsService.isQuicksy()
171                        && Config.QUICKSY_DOMAIN.equals(jid.getDomain())
172                        && jid.getLocal() != null;
173        final String value;
174        if (quicksyContact) {
175            value = PhoneNumberUtilWrapper.toFormattedPhoneNumber(this, jid);
176        } else {
177            value = jid.toString();
178        }
179        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
180        builder.setTitle(getString(R.string.action_add_phone_book));
181        builder.setMessage(getString(R.string.add_phone_book_text, value));
182        builder.setNegativeButton(getString(R.string.cancel), null);
183        builder.setPositiveButton(
184                getString(R.string.add),
185                (dialog, which) -> {
186                    final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
187                    intent.setType(Contacts.CONTENT_ITEM_TYPE);
188                    if (quicksyContact) {
189                        intent.putExtra(Intents.Insert.PHONE, value);
190                    } else {
191                        intent.putExtra(Intents.Insert.IM_HANDLE, value);
192                        intent.putExtra(
193                                Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER);
194                        // TODO for modern use we want PROTOCOL_CUSTOM and an extra field with a
195                        // value of 'XMPP'
196                        // however we don’t have such a field and thus have to use the legacy
197                        // PROTOCOL_JABBER
198                    }
199                    intent.putExtra("finishActivityOnSaveCompleted", true);
200                    try {
201                        startActivityForResult(intent, 0);
202                    } catch (ActivityNotFoundException e) {
203                        Toast.makeText(
204                                        ContactDetailsActivity.this,
205                                        R.string.no_application_found_to_view_contact,
206                                        Toast.LENGTH_SHORT)
207                                .show();
208                    }
209                });
210        builder.create().show();
211    }
212
213    @Override
214    public void onRosterUpdate() {
215        refreshUi();
216    }
217
218    @Override
219    public void onAccountUpdate() {
220        refreshUi();
221    }
222
223    @Override
224    public void OnUpdateBlocklist(final Status status) {
225        refreshUi();
226    }
227
228    @Override
229    protected void refreshUiReal() {
230        invalidateOptionsMenu();
231        populateView();
232    }
233
234    @Override
235    protected String getShareableUri(boolean http) {
236        if (http) {
237            return "https://conversations.im/i/"
238                    + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toString());
239        } else {
240            return "xmpp:" + contact.getJid().asBareJid().toString();
241        }
242    }
243
244    @Override
245    protected void onCreate(final Bundle savedInstanceState) {
246        super.onCreate(savedInstanceState);
247        showInactiveOmemo =
248                savedInstanceState != null
249                        && savedInstanceState.getBoolean("show_inactive_omemo", false);
250        if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) {
251            try {
252                this.accountJid = Jid.of(getIntent().getExtras().getString(EXTRA_ACCOUNT));
253            } catch (final IllegalArgumentException ignored) {
254            }
255            try {
256                this.contactJid = Jid.of(getIntent().getExtras().getString("contact"));
257            } catch (final IllegalArgumentException ignored) {
258            }
259        }
260        this.messageFingerprint = getIntent().getStringExtra("fingerprint");
261        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_contact_details);
262        Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
263
264        setSupportActionBar(binding.toolbar);
265        configureActionBar(getSupportActionBar());
266        binding.showInactiveDevices.setOnClickListener(
267                v -> {
268                    showInactiveOmemo = !showInactiveOmemo;
269                    populateView();
270                });
271        binding.addContactButton.setOnClickListener(v -> showAddToRosterDialog(contact));
272
273        mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
274        this.binding.media.setAdapter(mMediaAdapter);
275        GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size);
276    }
277
278    @Override
279    public void onSaveInstanceState(final Bundle savedInstanceState) {
280        savedInstanceState.putBoolean("show_inactive_omemo", showInactiveOmemo);
281        super.onSaveInstanceState(savedInstanceState);
282    }
283
284    @Override
285    public void onStart() {
286        super.onStart();
287        final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
288        this.showDynamicTags = preferences.getBoolean(AppSettings.SHOW_DYNAMIC_TAGS, false);
289        this.showLastSeen = preferences.getBoolean("last_activity", false);
290        binding.mediaWrapper.setVisibility(
291                Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
292        mMediaAdapter.setAttachments(Collections.emptyList());
293    }
294
295    @Override
296    public void onRequestPermissionsResult(
297            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
298        // TODO check for Camera / Scan permission
299        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
300        if (grantResults.length > 0)
301            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
302                if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
303                    showAddToPhoneBookDialog();
304                    xmppConnectionService.loadPhoneContacts();
305                    xmppConnectionService.startContactObserver();
306                }
307            }
308    }
309
310    @Override
311    public boolean onOptionsItemSelected(final MenuItem menuItem) {
312        if (MenuDoubleTabUtil.shouldIgnoreTap()) {
313            return false;
314        }
315        switch (menuItem.getItemId()) {
316            case android.R.id.home:
317                finish();
318                break;
319            case R.id.action_share_http:
320                shareLink(true);
321                break;
322            case R.id.action_share_uri:
323                shareLink(false);
324                break;
325            case R.id.action_delete_contact:
326                final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
327                builder.setNegativeButton(getString(R.string.cancel), null);
328                builder.setTitle(getString(R.string.action_delete_contact))
329                        .setMessage(
330                                JidDialog.style(
331                                        this,
332                                        R.string.remove_contact_text,
333                                        contact.getJid().toString()))
334                        .setPositiveButton(getString(R.string.delete), removeFromRoster)
335                        .create()
336                        .show();
337                break;
338            case R.id.action_edit_contact:
339                final Uri systemAccount = contact.getSystemAccount();
340                if (systemAccount == null) {
341                    quickEdit(
342                            contact.getServerName(),
343                            R.string.contact_name,
344                            value -> {
345                                contact.setServerName(value);
346                                ContactDetailsActivity.this.xmppConnectionService
347                                        .pushContactToServer(contact);
348                                populateView();
349                                return null;
350                            },
351                            true);
352                } else {
353                    Intent intent = new Intent(Intent.ACTION_EDIT);
354                    intent.setDataAndType(systemAccount, Contacts.CONTENT_ITEM_TYPE);
355                    intent.putExtra("finishActivityOnSaveCompleted", true);
356                    try {
357                        startActivity(intent);
358                    } catch (ActivityNotFoundException e) {
359                        Toast.makeText(
360                                        ContactDetailsActivity.this,
361                                        R.string.no_application_found_to_view_contact,
362                                        Toast.LENGTH_SHORT)
363                                .show();
364                    }
365                }
366                break;
367            case R.id.action_block, R.id.action_unblock:
368                BlockContactDialog.show(this, contact);
369                break;
370            case R.id.action_custom_notifications:
371                configureCustomNotifications(contact);
372                break;
373        }
374        return super.onOptionsItemSelected(menuItem);
375    }
376
377    private void configureCustomNotifications(final Contact contact) {
378        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
379            return;
380        }
381        final var shortcut = xmppConnectionService.getShortcutService().getShortcutInfo(contact);
382        configureCustomNotification(shortcut);
383    }
384
385    @Override
386    public boolean onCreateOptionsMenu(final Menu menu) {
387        getMenuInflater().inflate(R.menu.contact_details, menu);
388        AccountUtils.showHideMenuItems(menu);
389        final MenuItem block = menu.findItem(R.id.action_block);
390        final MenuItem unblock = menu.findItem(R.id.action_unblock);
391        final MenuItem edit = menu.findItem(R.id.action_edit_contact);
392        final MenuItem delete = menu.findItem(R.id.action_delete_contact);
393        final MenuItem customNotifications = menu.findItem(R.id.action_custom_notifications);
394        customNotifications.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
395        if (contact == null) {
396            return true;
397        }
398        final XmppConnection connection = contact.getAccount().getXmppConnection();
399        if (connection != null && connection.getFeatures().blocking()) {
400            if (this.contact.isBlocked()) {
401                block.setVisible(false);
402            } else {
403                unblock.setVisible(false);
404            }
405        } else {
406            unblock.setVisible(false);
407            block.setVisible(false);
408        }
409        if (!contact.showInRoster()) {
410            edit.setVisible(false);
411            delete.setVisible(false);
412        }
413        return super.onCreateOptionsMenu(menu);
414    }
415
416    private void populateView() {
417        if (contact == null) {
418            return;
419        }
420        invalidateOptionsMenu();
421        setTitle(contact.getDisplayName());
422        if (contact.showInRoster()) {
423            binding.detailsSendPresence.setVisibility(View.VISIBLE);
424            binding.detailsReceivePresence.setVisibility(View.VISIBLE);
425            binding.addContactButton.setVisibility(View.GONE);
426            binding.detailsSendPresence.setOnCheckedChangeListener(null);
427            binding.detailsReceivePresence.setOnCheckedChangeListener(null);
428
429            Collection<String> statusMessages = contact.getPresences().getStatusMessages();
430            if (statusMessages.isEmpty()) {
431                binding.statusMessage.setVisibility(View.GONE);
432            } else if (statusMessages.size() == 1) {
433                final String message = Iterables.getOnlyElement(statusMessages);
434                binding.statusMessage.setVisibility(View.VISIBLE);
435                final Spannable span = new SpannableString(message);
436                if (Emoticons.isOnlyEmoji(message)) {
437                    span.setSpan(
438                            new RelativeSizeSpan(2.0f),
439                            0,
440                            message.length(),
441                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
442                }
443                binding.statusMessage.setText(span);
444            } else {
445                binding.statusMessage.setText(Joiner.on('\n').join(statusMessages));
446            }
447
448            if (contact.getOption(Contact.Options.FROM)) {
449                binding.detailsSendPresence.setText(R.string.send_presence_updates);
450                binding.detailsSendPresence.setChecked(true);
451            } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
452                binding.detailsSendPresence.setChecked(false);
453                binding.detailsSendPresence.setText(R.string.send_presence_updates);
454            } else {
455                binding.detailsSendPresence.setText(R.string.preemptively_grant);
456                binding.detailsSendPresence.setChecked(
457                        contact.getOption(Contact.Options.PREEMPTIVE_GRANT));
458            }
459            if (contact.getOption(Contact.Options.TO)) {
460                binding.detailsReceivePresence.setText(R.string.receive_presence_updates);
461                binding.detailsReceivePresence.setChecked(true);
462            } else {
463                binding.detailsReceivePresence.setText(R.string.ask_for_presence_updates);
464                binding.detailsReceivePresence.setChecked(
465                        contact.getOption(Contact.Options.ASKING));
466            }
467            if (contact.getAccount().isOnlineAndConnected()) {
468                binding.detailsReceivePresence.setEnabled(true);
469                binding.detailsSendPresence.setEnabled(true);
470            } else {
471                binding.detailsReceivePresence.setEnabled(false);
472                binding.detailsSendPresence.setEnabled(false);
473            }
474            binding.detailsSendPresence.setOnCheckedChangeListener(this.mOnSendCheckedChange);
475            binding.detailsReceivePresence.setOnCheckedChangeListener(this.mOnReceiveCheckedChange);
476        } else {
477            binding.addContactButton.setVisibility(View.VISIBLE);
478            binding.detailsSendPresence.setVisibility(View.GONE);
479            binding.detailsReceivePresence.setVisibility(View.GONE);
480            binding.statusMessage.setVisibility(View.GONE);
481        }
482
483        if (contact.isBlocked() && !this.showDynamicTags) {
484            binding.detailsLastseen.setVisibility(View.VISIBLE);
485            binding.detailsLastseen.setText(R.string.contact_blocked);
486        } else {
487            if (showLastSeen
488                    && contact.getLastseen() > 0
489                    && contact.getPresences().allOrNonSupport(Namespace.IDLE)) {
490                binding.detailsLastseen.setVisibility(View.VISIBLE);
491                binding.detailsLastseen.setText(
492                        UIHelper.lastseen(
493                                getApplicationContext(),
494                                contact.isActive(),
495                                contact.getLastseen()));
496            } else {
497                binding.detailsLastseen.setVisibility(View.GONE);
498            }
499        }
500
501        binding.detailsContactjid.setText(IrregularUnicodeDetector.style(this, contact.getJid()));
502        final String account = contact.getAccount().getJid().asBareJid().toString();
503        binding.detailsAccount.setText(getString(R.string.using_account, account));
504        AvatarWorkerTask.loadAvatar(
505                contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size);
506        binding.detailsContactBadge.setOnClickListener(this::onBadgeClick);
507
508        binding.detailsContactKeys.removeAllViews();
509        boolean hasKeys = false;
510        final LayoutInflater inflater = getLayoutInflater();
511        final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
512        if (Config.supportOmemo() && axolotlService != null) {
513            final Collection<XmppAxolotlSession> sessions =
514                    axolotlService.findSessionsForContact(contact);
515            boolean anyActive = false;
516            for (XmppAxolotlSession session : sessions) {
517                anyActive = session.getTrust().isActive();
518                if (anyActive) {
519                    break;
520                }
521            }
522            boolean skippedInactive = false;
523            boolean showsInactive = false;
524            boolean showUnverifiedWarning = false;
525            for (final XmppAxolotlSession session : sessions) {
526                final FingerprintStatus trust = session.getTrust();
527                hasKeys |= !trust.isCompromised();
528                if (!trust.isActive() && anyActive) {
529                    if (showInactiveOmemo) {
530                        showsInactive = true;
531                    } else {
532                        skippedInactive = true;
533                        continue;
534                    }
535                }
536                if (!trust.isCompromised()) {
537                    boolean highlight = session.getFingerprint().equals(messageFingerprint);
538                    addFingerprintRow(binding.detailsContactKeys, session, highlight);
539                }
540                if (trust.isUnverified()) {
541                    showUnverifiedWarning = true;
542                }
543            }
544            binding.unverifiedWarning.setVisibility(
545                    showUnverifiedWarning ? View.VISIBLE : View.GONE);
546            if (showsInactive || skippedInactive) {
547                binding.showInactiveDevices.setText(
548                        showsInactive
549                                ? R.string.hide_inactive_devices
550                                : R.string.show_inactive_devices);
551                binding.showInactiveDevices.setVisibility(View.VISIBLE);
552            } else {
553                binding.showInactiveDevices.setVisibility(View.GONE);
554            }
555        } else {
556            binding.showInactiveDevices.setVisibility(View.GONE);
557        }
558        final boolean isCameraFeatureAvailable = isCameraFeatureAvailable();
559        binding.scanButton.setVisibility(
560                hasKeys && isCameraFeatureAvailable ? View.VISIBLE : View.GONE);
561        if (hasKeys) {
562            binding.scanButton.setOnClickListener((v) -> ScanActivity.scan(this));
563        }
564        if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) {
565            hasKeys = true;
566            View view =
567                    inflater.inflate(
568                            R.layout.item_device_fingerprint, binding.detailsContactKeys, false);
569            TextView key = view.findViewById(R.id.key);
570            TextView keyType = view.findViewById(R.id.key_type);
571            keyType.setText(R.string.openpgp_key_id);
572            if ("pgp".equals(messageFingerprint)) {
573                keyType.setTextColor(
574                        MaterialColors.getColor(
575                                keyType, com.google.android.material.R.attr.colorPrimaryVariant));
576            }
577            key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
578            final OnClickListener openKey = v -> launchOpenKeyChain(contact.getPgpKeyId());
579            view.setOnClickListener(openKey);
580            key.setOnClickListener(openKey);
581            keyType.setOnClickListener(openKey);
582            binding.detailsContactKeys.addView(view);
583        }
584        binding.keysWrapper.setVisibility(hasKeys ? View.VISIBLE : View.GONE);
585
586        final List<ListItem.Tag> tagList = contact.getTags(this);
587        final boolean hasMetaTags =
588                contact.isBlocked() || contact.getShownStatus() != Presence.Availability.OFFLINE;
589        if ((tagList.isEmpty() && !hasMetaTags) || !this.showDynamicTags) {
590            binding.tags.setVisibility(View.GONE);
591        } else {
592            binding.tags.setVisibility(View.VISIBLE);
593            binding.tags.removeViews(1, binding.tags.getChildCount() - 1);
594            final ImmutableList.Builder<Integer> viewIdBuilder = new ImmutableList.Builder<>();
595            for (final ListItem.Tag tag : tagList) {
596                final String name = tag.getName();
597                final TextView tv =
598                        (TextView) inflater.inflate(R.layout.item_tag, binding.tags, false);
599                tv.setText(name);
600                tv.setBackgroundTintList(
601                        ColorStateList.valueOf(
602                                MaterialColors.harmonizeWithPrimary(
603                                        this, XEP0392Helper.rgbFromNick(name))));
604                final int id = ViewCompat.generateViewId();
605                tv.setId(id);
606                viewIdBuilder.add(id);
607                binding.tags.addView(tv);
608            }
609            if (contact.isBlocked()) {
610                final TextView tv =
611                        (TextView) inflater.inflate(R.layout.item_tag, binding.tags, false);
612                tv.setText(R.string.blocked);
613                tv.setBackgroundTintList(
614                        ColorStateList.valueOf(
615                                MaterialColors.harmonizeWithPrimary(
616                                        tv.getContext(),
617                                        ContextCompat.getColor(
618                                                tv.getContext(), R.color.gray_800))));
619                final int id = ViewCompat.generateViewId();
620                tv.setId(id);
621                viewIdBuilder.add(id);
622                binding.tags.addView(tv);
623            } else {
624                final Presence.Availability status = contact.getShownStatus();
625                if (status != Presence.Availability.OFFLINE) {
626                    final TextView tv =
627                            (TextView) inflater.inflate(R.layout.item_tag, binding.tags, false);
628                    UIHelper.setStatus(tv, status);
629                    final int id = ViewCompat.generateViewId();
630                    tv.setId(id);
631                    viewIdBuilder.add(id);
632                    binding.tags.addView(tv);
633                }
634            }
635            binding.flowWidget.setReferencedIds(Ints.toArray(viewIdBuilder.build()));
636        }
637    }
638
639    private void onBadgeClick(final View view) {
640        if (QuickConversationsService.isContactListIntegration(this)) {
641            final Uri systemAccount = contact.getSystemAccount();
642            if (systemAccount == null) {
643                checkContactPermissionAndShowAddDialog();
644            } else {
645                final Intent intent = new Intent(Intent.ACTION_VIEW);
646                intent.setData(systemAccount);
647                try {
648                    startActivity(intent);
649                } catch (final ActivityNotFoundException e) {
650                    Toast.makeText(
651                                    this,
652                                    R.string.no_application_found_to_view_contact,
653                                    Toast.LENGTH_SHORT)
654                            .show();
655                }
656            }
657        } else {
658            Toast.makeText(
659                            this,
660                            R.string.contact_list_integration_not_available,
661                            Toast.LENGTH_SHORT)
662                    .show();
663        }
664    }
665
666    public void onBackendConnected() {
667        if (accountJid != null && contactJid != null) {
668            Account account = xmppConnectionService.findAccountByJid(accountJid);
669            if (account == null) {
670                return;
671            }
672            this.contact = account.getRoster().getContact(contactJid);
673            if (mPendingFingerprintVerificationUri != null) {
674                processFingerprintVerification(mPendingFingerprintVerificationUri);
675                mPendingFingerprintVerificationUri = null;
676            }
677
678            if (Compatibility.hasStoragePermission(this)) {
679                final int limit = GridManager.getCurrentColumnCount(this.binding.media);
680                xmppConnectionService.getAttachments(
681                        account, contact.getJid().asBareJid(), limit, this);
682                this.binding.showMedia.setOnClickListener(
683                        (v) -> MediaBrowserActivity.launch(this, contact));
684            }
685            populateView();
686        }
687    }
688
689    @Override
690    public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
691        refreshUi();
692    }
693
694    @Override
695    protected void processFingerprintVerification(XmppUri uri) {
696        if (contact != null
697                && contact.getJid().asBareJid().equals(uri.getJid())
698                && uri.hasFingerprints()) {
699            if (xmppConnectionService.verifyFingerprints(contact, uri.getFingerprints())) {
700                Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
701            }
702        } else {
703            Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
704        }
705    }
706
707    @Override
708    public void onMediaLoaded(List<Attachment> attachments) {
709        runOnUiThread(
710                () -> {
711                    int limit = GridManager.getCurrentColumnCount(binding.media);
712                    mMediaAdapter.setAttachments(
713                            attachments.subList(0, Math.min(limit, attachments.size())));
714                    binding.mediaWrapper.setVisibility(
715                            attachments.size() > 0 ? View.VISIBLE : View.GONE);
716                });
717    }
718}