MucOptions.java

   1package eu.siacs.conversations.entities;
   2
   3import androidx.annotation.NonNull;
   4import androidx.annotation.Nullable;
   5import com.google.common.base.Strings;
   6import com.google.common.collect.ImmutableList;
   7import com.google.common.collect.Iterables;
   8import eu.siacs.conversations.Config;
   9import eu.siacs.conversations.R;
  10import eu.siacs.conversations.services.AvatarService;
  11import eu.siacs.conversations.services.MessageArchiveService;
  12import eu.siacs.conversations.utils.JidHelper;
  13import eu.siacs.conversations.utils.UIHelper;
  14import eu.siacs.conversations.xml.Namespace;
  15import eu.siacs.conversations.xmpp.Jid;
  16import eu.siacs.conversations.xmpp.chatstate.ChatState;
  17import eu.siacs.conversations.xmpp.pep.Avatar;
  18import im.conversations.android.xmpp.model.data.Data;
  19import im.conversations.android.xmpp.model.data.Field;
  20import im.conversations.android.xmpp.model.disco.info.InfoQuery;
  21import java.util.ArrayList;
  22import java.util.Arrays;
  23import java.util.Collection;
  24import java.util.Collections;
  25import java.util.HashSet;
  26import java.util.List;
  27import java.util.Locale;
  28import java.util.Objects;
  29import java.util.Set;
  30
  31public class MucOptions {
  32
  33    public static final String STATUS_CODE_SELF_PRESENCE = "110";
  34    public static final String STATUS_CODE_ROOM_CREATED = "201";
  35    public static final String STATUS_CODE_BANNED = "301";
  36    public static final String STATUS_CODE_CHANGED_NICK = "303";
  37    public static final String STATUS_CODE_KICKED = "307";
  38    public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
  39    public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
  40    public static final String STATUS_CODE_SHUTDOWN = "332";
  41    public static final String STATUS_CODE_TECHNICAL_REASONS = "333";
  42    private final Set<User> users = new HashSet<>();
  43    private final Conversation conversation;
  44    public OnRenameListener onRenameListener = null;
  45    private boolean mAutoPushConfiguration = true;
  46    private final Account account;
  47    private InfoQuery infoQuery;
  48    private boolean isOnline = false;
  49    private Error error = Error.NONE;
  50    private User self;
  51    private String password = null;
  52
  53    public MucOptions(final Conversation conversation) {
  54        this.account = conversation.getAccount();
  55        this.conversation = conversation;
  56        this.self = new User(this, createJoinJid(getProposedNick()));
  57        this.self.affiliation = Affiliation.of(conversation.getAttribute("affiliation"));
  58        this.self.role = Role.of(conversation.getAttribute("role"));
  59    }
  60
  61    public Account getAccount() {
  62        return this.conversation.getAccount();
  63    }
  64
  65    public boolean setSelf(final User user) {
  66        this.self = user;
  67        final boolean roleChanged = this.conversation.setAttribute("role", user.role.toString());
  68        final boolean affiliationChanged =
  69                this.conversation.setAttribute("affiliation", user.affiliation.toString());
  70        return roleChanged || affiliationChanged;
  71    }
  72
  73    public void changeAffiliation(final Jid jid, final Affiliation affiliation) {
  74        final User user = findUserByRealJid(jid);
  75        synchronized (users) {
  76            if (user != null && user.getRole() == Role.NONE) {
  77                users.remove(user);
  78                if (affiliation.ranks(Affiliation.MEMBER)) {
  79                    user.affiliation = affiliation;
  80                    users.add(user);
  81                }
  82            }
  83        }
  84    }
  85
  86    public void flagNoAutoPushConfiguration() {
  87        mAutoPushConfiguration = false;
  88    }
  89
  90    public boolean autoPushConfiguration() {
  91        return mAutoPushConfiguration;
  92    }
  93
  94    public boolean isSelf(final Jid counterpart) {
  95        return counterpart.equals(self.getFullJid());
  96    }
  97
  98    public boolean isSelf(final String occupantId) {
  99        return occupantId.equals(self.getOccupantId());
 100    }
 101
 102    public void resetChatState() {
 103        synchronized (users) {
 104            for (User user : users) {
 105                user.chatState = Config.DEFAULT_CHAT_STATE;
 106            }
 107        }
 108    }
 109
 110    public boolean mamSupport() {
 111        return MessageArchiveService.Version.has(getFeatures());
 112    }
 113
 114    private InfoQuery getServiceDiscoveryResult() {
 115        return this.infoQuery;
 116    }
 117
 118    public boolean updateConfiguration(final InfoQuery serviceDiscoveryResult) {
 119        this.infoQuery = serviceDiscoveryResult;
 120        final var roomInfo = getRoomInfoForm();
 121        String name;
 122        Field roomConfigName =
 123                roomInfo == null ? null : roomInfo.getFieldByName("muc#roomconfig_roomname");
 124        if (roomConfigName != null) {
 125            name = roomConfigName.getValue();
 126        } else {
 127            final var identities = serviceDiscoveryResult.getIdentities();
 128            final String identityName =
 129                    !identities.isEmpty()
 130                            ? Iterables.getFirst(identities, null).getIdentityName()
 131                            : null;
 132            final Jid jid = conversation.getJid();
 133            if (identityName != null && !identityName.equals(jid == null ? null : jid.getLocal())) {
 134                name = identityName;
 135            } else {
 136                name = null;
 137            }
 138        }
 139        boolean changed = conversation.setAttribute("muc_name", name);
 140        changed |=
 141                conversation.setAttribute(
 142                        Conversation.ATTRIBUTE_MEMBERS_ONLY, this.hasFeature("muc_membersonly"));
 143        changed |=
 144                conversation.setAttribute(
 145                        Conversation.ATTRIBUTE_MODERATED, this.hasFeature("muc_moderated"));
 146        changed |=
 147                conversation.setAttribute(
 148                        Conversation.ATTRIBUTE_NON_ANONYMOUS, this.hasFeature("muc_nonanonymous"));
 149        return changed;
 150    }
 151
 152    private Data getRoomInfoForm() {
 153        final var serviceDiscoveryResult = getServiceDiscoveryResult();
 154        return serviceDiscoveryResult == null
 155                ? null
 156                : serviceDiscoveryResult.getServiceDiscoveryExtension(
 157                        "http://jabber.org/protocol/muc#roominfo");
 158    }
 159
 160    public String getAvatar() {
 161        return account.getRoster().getContact(conversation.getJid()).getAvatarFilename();
 162    }
 163
 164    public boolean hasFeature(String feature) {
 165        final var serviceDiscoveryResult = getServiceDiscoveryResult();
 166        return serviceDiscoveryResult != null
 167                && serviceDiscoveryResult.getFeatureStrings().contains(feature);
 168    }
 169
 170    public boolean hasVCards() {
 171        return hasFeature("vcard-temp");
 172    }
 173
 174    public boolean canInvite() {
 175        final boolean hasPermission =
 176                !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites();
 177        return hasPermission && online();
 178    }
 179
 180    public boolean allowInvites() {
 181        final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
 182        return field != null && "1".equals(field.getValue());
 183    }
 184
 185    public boolean canChangeSubject() {
 186        return self.getRole().ranks(Role.MODERATOR) || participantsCanChangeSubject();
 187    }
 188
 189    public boolean participantsCanChangeSubject() {
 190        final Field configField = getRoomInfoForm().getFieldByName("muc#roomconfig_changesubject");
 191        final Field infoField = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject");
 192        final Field field = configField != null ? configField : infoField;
 193        return field != null && "1".equals(field.getValue());
 194    }
 195
 196    public boolean allowPm() {
 197        final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm");
 198        if (field == null) {
 199            return true; // fall back if field does not exists
 200        }
 201        if ("anyone".equals(field.getValue())) {
 202            return true;
 203        } else if ("participants".equals(field.getValue())) {
 204            return self.getRole().ranks(Role.PARTICIPANT);
 205        } else if ("moderators".equals(field.getValue())) {
 206            return self.getRole().ranks(Role.MODERATOR);
 207        } else {
 208            return false;
 209        }
 210    }
 211
 212    public boolean allowPmRaw() {
 213        final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm");
 214        return field == null || Arrays.asList("anyone", "participants").contains(field.getValue());
 215    }
 216
 217    public boolean participating() {
 218        return self.getRole().ranks(Role.PARTICIPANT) || !moderated();
 219    }
 220
 221    public boolean membersOnly() {
 222        return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, false);
 223    }
 224
 225    public Collection<String> getFeatures() {
 226        final var serviceDiscoveryResult = getServiceDiscoveryResult();
 227        return serviceDiscoveryResult != null
 228                ? serviceDiscoveryResult.getFeatureStrings()
 229                : Collections.emptyList();
 230    }
 231
 232    public boolean nonanonymous() {
 233        return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_NON_ANONYMOUS, false);
 234    }
 235
 236    public boolean isPrivateAndNonAnonymous() {
 237        return membersOnly() && nonanonymous();
 238    }
 239
 240    public boolean moderated() {
 241        return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MODERATED, false);
 242    }
 243
 244    public boolean stableId() {
 245        return getFeatures().contains("http://jabber.org/protocol/muc#stable_id");
 246    }
 247
 248    public boolean occupantId() {
 249        final var features = getFeatures();
 250        return features.contains(Namespace.OCCUPANT_ID);
 251    }
 252
 253    public User deleteUser(Jid jid) {
 254        User user = findUserByFullJid(jid);
 255        if (user != null) {
 256            synchronized (users) {
 257                users.remove(user);
 258                boolean realJidInMuc = false;
 259                for (User u : users) {
 260                    if (user.realJid != null && user.realJid.equals(u.realJid)) {
 261                        realJidInMuc = true;
 262                        break;
 263                    }
 264                }
 265                boolean self =
 266                        user.realJid != null && user.realJid.equals(account.getJid().asBareJid());
 267                if (membersOnly()
 268                        && nonanonymous()
 269                        && user.affiliation.ranks(Affiliation.MEMBER)
 270                        && user.realJid != null
 271                        && !realJidInMuc
 272                        && !self) {
 273                    user.role = Role.NONE;
 274                    user.avatar = null;
 275                    user.fullJid = null;
 276                    users.add(user);
 277                }
 278            }
 279        }
 280        return user;
 281    }
 282
 283    // returns true if real jid was new;
 284    public boolean updateUser(User user) {
 285        User old;
 286        boolean realJidFound = false;
 287        if (user.fullJid == null && user.realJid != null) {
 288            old = findUserByRealJid(user.realJid);
 289            realJidFound = old != null;
 290            if (old != null) {
 291                if (old.fullJid != null) {
 292                    return false; // don't add. user already exists
 293                } else {
 294                    synchronized (users) {
 295                        users.remove(old);
 296                    }
 297                }
 298            }
 299        } else if (user.realJid != null) {
 300            old = findUserByRealJid(user.realJid);
 301            realJidFound = old != null;
 302            synchronized (users) {
 303                if (old != null && (old.fullJid == null || old.role == Role.NONE)) {
 304                    users.remove(old);
 305                }
 306            }
 307        }
 308        old = findUserByFullJid(user.getFullJid());
 309
 310        synchronized (this.users) {
 311            if (old != null) {
 312                users.remove(old);
 313            }
 314            boolean fullJidIsSelf =
 315                    isOnline
 316                            && user.getFullJid() != null
 317                            && user.getFullJid().equals(self.getFullJid());
 318            if ((!membersOnly() || user.getAffiliation().ranks(Affiliation.MEMBER))
 319                    && user.getAffiliation().outranks(Affiliation.OUTCAST)
 320                    && !fullJidIsSelf) {
 321                this.users.add(user);
 322                return !realJidFound && user.realJid != null;
 323            }
 324        }
 325        return false;
 326    }
 327
 328    public User findUserByFullJid(Jid jid) {
 329        if (jid == null) {
 330            return null;
 331        }
 332        synchronized (users) {
 333            for (User user : users) {
 334                if (jid.equals(user.getFullJid())) {
 335                    return user;
 336                }
 337            }
 338        }
 339        return null;
 340    }
 341
 342    public User findUserByRealJid(Jid jid) {
 343        if (jid == null) {
 344            return null;
 345        }
 346        synchronized (users) {
 347            for (User user : users) {
 348                if (jid.equals(user.realJid)) {
 349                    return user;
 350                }
 351            }
 352        }
 353        return null;
 354    }
 355
 356    public User findUserByOccupantId(final String occupantId) {
 357        synchronized (this.users) {
 358            return Strings.isNullOrEmpty(occupantId)
 359                    ? null
 360                    : Iterables.find(this.users, u -> occupantId.equals(u.occupantId), null);
 361        }
 362    }
 363
 364    public User findOrCreateUserByRealJid(Jid jid, Jid fullJid) {
 365        final User existing = findUserByRealJid(jid);
 366        if (existing != null) {
 367            return existing;
 368        }
 369        final var user = new User(this, fullJid);
 370        user.setRealJid(jid);
 371        return user;
 372    }
 373
 374    public User findUser(ReadByMarker readByMarker) {
 375        if (readByMarker.getRealJid() != null) {
 376            return findOrCreateUserByRealJid(
 377                    readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid());
 378        } else if (readByMarker.getFullJid() != null) {
 379            return findUserByFullJid(readByMarker.getFullJid());
 380        } else {
 381            return null;
 382        }
 383    }
 384
 385    private User findUser(final Reaction reaction) {
 386        if (reaction.trueJid != null) {
 387            return findOrCreateUserByRealJid(reaction.trueJid.asBareJid(), reaction.from);
 388        }
 389        final var existing = findUserByOccupantId(reaction.occupantId);
 390        if (existing != null) {
 391            return existing;
 392        } else if (reaction.from != null) {
 393            return new User(this, reaction.from);
 394        } else {
 395            return null;
 396        }
 397    }
 398
 399    public List<User> findUsers(final Collection<Reaction> reactions) {
 400        final ImmutableList.Builder<User> builder = new ImmutableList.Builder<>();
 401        for (final Reaction reaction : reactions) {
 402            final var user = findUser(reaction);
 403            if (user != null) {
 404                builder.add(user);
 405            }
 406        }
 407        return builder.build();
 408    }
 409
 410    public boolean isContactInRoom(Contact contact) {
 411        return contact != null && findUserByRealJid(contact.getJid().asBareJid()) != null;
 412    }
 413
 414    public boolean isUserInRoom(Jid jid) {
 415        return findUserByFullJid(jid) != null;
 416    }
 417
 418    public boolean setOnline() {
 419        boolean before = this.isOnline;
 420        this.isOnline = true;
 421        return !before;
 422    }
 423
 424    public ArrayList<User> getUsers() {
 425        return getUsers(true);
 426    }
 427
 428    public ArrayList<User> getUsers(boolean includeOffline) {
 429        synchronized (users) {
 430            ArrayList<User> users = new ArrayList<>();
 431            for (User user : this.users) {
 432                if (!user.isDomain()
 433                        && (includeOffline || user.getRole().ranks(Role.PARTICIPANT))) {
 434                    users.add(user);
 435                }
 436            }
 437            return users;
 438        }
 439    }
 440
 441    public ArrayList<User> getUsersWithChatState(ChatState state, int max) {
 442        synchronized (users) {
 443            ArrayList<User> list = new ArrayList<>();
 444            for (User user : users) {
 445                if (user.chatState == state) {
 446                    list.add(user);
 447                    if (list.size() >= max) {
 448                        break;
 449                    }
 450                }
 451            }
 452            return list;
 453        }
 454    }
 455
 456    public List<User> getUsers(final int max) {
 457        final ArrayList<User> subset = new ArrayList<>();
 458        final HashSet<Jid> addresses = new HashSet<>();
 459        addresses.add(account.getJid().asBareJid());
 460        synchronized (users) {
 461            for (User user : users) {
 462                if (user.getRealJid() == null
 463                        || (user.getRealJid().getLocal() != null
 464                                && addresses.add(user.getRealJid()))) {
 465                    subset.add(user);
 466                }
 467                if (subset.size() >= max) {
 468                    break;
 469                }
 470            }
 471        }
 472        return subset;
 473    }
 474
 475    public static List<User> sub(List<User> users, int max) {
 476        ArrayList<User> subset = new ArrayList<>();
 477        HashSet<Jid> jids = new HashSet<>();
 478        for (User user : users) {
 479            jids.add(user.getAccount().getJid().asBareJid());
 480            if (user.getRealJid() == null
 481                    || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) {
 482                subset.add(user);
 483            }
 484            if (subset.size() >= max) {
 485                break;
 486            }
 487        }
 488        return subset;
 489    }
 490
 491    public int getUserCount() {
 492        synchronized (users) {
 493            return users.size();
 494        }
 495    }
 496
 497    private String getProposedNick() {
 498        final Bookmark bookmark = this.conversation.getBookmark();
 499        if (bookmark != null) {
 500            // if we already have a bookmark we consider this the source of truth
 501            return getProposedNickPure();
 502        }
 503        final var storedJid = conversation.getJid();
 504        if (storedJid.isBareJid()) {
 505            return defaultNick(account);
 506        } else {
 507            return storedJid.getResource();
 508        }
 509    }
 510
 511    public String getProposedNickPure() {
 512        final Bookmark bookmark = this.conversation.getBookmark();
 513        final String bookmarkedNick =
 514                normalize(account.getJid(), bookmark == null ? null : bookmark.getNick());
 515        if (bookmarkedNick != null) {
 516            return bookmarkedNick;
 517        } else {
 518            return defaultNick(account);
 519        }
 520    }
 521
 522    public static String defaultNick(final Account account) {
 523        final String displayName = normalize(account.getJid(), account.getDisplayName());
 524        if (displayName == null) {
 525            return JidHelper.localPartOrFallback(account.getJid());
 526        } else {
 527            return displayName;
 528        }
 529    }
 530
 531    private static String normalize(final Jid account, final String nick) {
 532        if (account == null || Strings.isNullOrEmpty(nick)) {
 533            return null;
 534        }
 535        try {
 536            return account.withResource(nick).getResource();
 537        } catch (final IllegalArgumentException e) {
 538            return null;
 539        }
 540    }
 541
 542    public String getActualNick() {
 543        if (this.self.getName() != null) {
 544            return this.self.getName();
 545        } else {
 546            return this.getProposedNick();
 547        }
 548    }
 549
 550    public boolean online() {
 551        return this.isOnline;
 552    }
 553
 554    public Error getError() {
 555        return this.error;
 556    }
 557
 558    public void setError(Error error) {
 559        this.isOnline = isOnline && error == Error.NONE;
 560        this.error = error;
 561    }
 562
 563    public void setOnRenameListener(OnRenameListener listener) {
 564        this.onRenameListener = listener;
 565    }
 566
 567    public void setOffline() {
 568        synchronized (users) {
 569            this.users.clear();
 570        }
 571        this.error = Error.NO_RESPONSE;
 572        this.isOnline = false;
 573    }
 574
 575    public User getSelf() {
 576        return self;
 577    }
 578
 579    public boolean setSubject(String subject) {
 580        return this.conversation.setAttribute("subject", subject);
 581    }
 582
 583    public String getSubject() {
 584        return this.conversation.getAttribute("subject");
 585    }
 586
 587    public String getName() {
 588        return this.conversation.getAttribute("muc_name");
 589    }
 590
 591    private List<User> getFallbackUsersFromCryptoTargets() {
 592        List<User> users = new ArrayList<>();
 593        for (Jid jid : conversation.getAcceptedCryptoTargets()) {
 594            User user = new User(this, null);
 595            user.setRealJid(jid);
 596            users.add(user);
 597        }
 598        return users;
 599    }
 600
 601    public List<User> getUsersRelevantForNameAndAvatar() {
 602        final List<User> users;
 603        if (isOnline) {
 604            users = getUsers(5);
 605        } else {
 606            users = getFallbackUsersFromCryptoTargets();
 607        }
 608        return users;
 609    }
 610
 611    String createNameFromParticipants() {
 612        List<User> users = getUsersRelevantForNameAndAvatar();
 613        if (users.size() >= 2) {
 614            StringBuilder builder = new StringBuilder();
 615            for (User user : users) {
 616                if (builder.length() != 0) {
 617                    builder.append(", ");
 618                }
 619                String name = UIHelper.getDisplayName(user);
 620                if (name != null) {
 621                    builder.append(name.split("\\s+")[0]);
 622                }
 623            }
 624            return builder.toString();
 625        } else {
 626            return null;
 627        }
 628    }
 629
 630    public long[] getPgpKeyIds() {
 631        List<Long> ids = new ArrayList<>();
 632        for (User user : this.users) {
 633            if (user.getPgpKeyId() != 0) {
 634                ids.add(user.getPgpKeyId());
 635            }
 636        }
 637        ids.add(account.getPgpId());
 638        long[] primitiveLongArray = new long[ids.size()];
 639        for (int i = 0; i < ids.size(); ++i) {
 640            primitiveLongArray[i] = ids.get(i);
 641        }
 642        return primitiveLongArray;
 643    }
 644
 645    public boolean pgpKeysInUse() {
 646        synchronized (users) {
 647            for (User user : users) {
 648                if (user.getPgpKeyId() != 0) {
 649                    return true;
 650                }
 651            }
 652        }
 653        return false;
 654    }
 655
 656    public boolean everybodyHasKeys() {
 657        synchronized (users) {
 658            for (User user : users) {
 659                if (user.getPgpKeyId() == 0) {
 660                    return false;
 661                }
 662            }
 663        }
 664        return true;
 665    }
 666
 667    public Jid createJoinJid(String nick) {
 668        try {
 669            return conversation.getJid().withResource(nick);
 670        } catch (final IllegalArgumentException e) {
 671            return null;
 672        }
 673    }
 674
 675    public Jid getTrueCounterpart(Jid jid) {
 676        if (jid.equals(getSelf().getFullJid())) {
 677            return account.getJid().asBareJid();
 678        }
 679        User user = findUserByFullJid(jid);
 680        return user == null ? null : user.realJid;
 681    }
 682
 683    public String getPassword() {
 684        this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
 685        if (this.password == null
 686                && conversation.getBookmark() != null
 687                && conversation.getBookmark().getPassword() != null) {
 688            return conversation.getBookmark().getPassword();
 689        } else {
 690            return this.password;
 691        }
 692    }
 693
 694    public void setPassword(String password) {
 695        if (conversation.getBookmark() != null) {
 696            conversation.getBookmark().setPassword(password);
 697        } else {
 698            this.password = password;
 699        }
 700        conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
 701    }
 702
 703    public Conversation getConversation() {
 704        return this.conversation;
 705    }
 706
 707    public List<Jid> getMembers(final boolean includeDomains) {
 708        ArrayList<Jid> members = new ArrayList<>();
 709        synchronized (users) {
 710            for (User user : users) {
 711                if (user.affiliation.ranks(Affiliation.MEMBER)
 712                        && user.realJid != null
 713                        && !user.realJid
 714                                .asBareJid()
 715                                .equals(conversation.account.getJid().asBareJid())
 716                        && (!user.isDomain() || includeDomains)) {
 717                    members.add(user.realJid);
 718                }
 719            }
 720        }
 721        return members;
 722    }
 723
 724    public enum Affiliation {
 725        OWNER(4, R.string.owner),
 726        ADMIN(3, R.string.admin),
 727        MEMBER(2, R.string.member),
 728        OUTCAST(0, R.string.outcast),
 729        NONE(1, R.string.no_affiliation);
 730
 731        private final int resId;
 732        private final int rank;
 733
 734        Affiliation(int rank, int resId) {
 735            this.resId = resId;
 736            this.rank = rank;
 737        }
 738
 739        public static Affiliation of(@Nullable String value) {
 740            if (value == null) {
 741                return NONE;
 742            }
 743            try {
 744                return Affiliation.valueOf(value.toUpperCase(Locale.US));
 745            } catch (IllegalArgumentException e) {
 746                return NONE;
 747            }
 748        }
 749
 750        public int getResId() {
 751            return resId;
 752        }
 753
 754        @Override
 755        public String toString() {
 756            return name().toLowerCase(Locale.US);
 757        }
 758
 759        public boolean outranks(Affiliation affiliation) {
 760            return rank > affiliation.rank;
 761        }
 762
 763        public boolean ranks(Affiliation affiliation) {
 764            return rank >= affiliation.rank;
 765        }
 766    }
 767
 768    public enum Role {
 769        MODERATOR(R.string.moderator, 3),
 770        VISITOR(R.string.visitor, 1),
 771        PARTICIPANT(R.string.participant, 2),
 772        NONE(R.string.no_role, 0);
 773
 774        private final int resId;
 775        private final int rank;
 776
 777        Role(int resId, int rank) {
 778            this.resId = resId;
 779            this.rank = rank;
 780        }
 781
 782        public static Role of(@Nullable String value) {
 783            if (value == null) {
 784                return NONE;
 785            }
 786            try {
 787                return Role.valueOf(value.toUpperCase(Locale.US));
 788            } catch (IllegalArgumentException e) {
 789                return NONE;
 790            }
 791        }
 792
 793        public int getResId() {
 794            return resId;
 795        }
 796
 797        @Override
 798        public String toString() {
 799            return name().toLowerCase(Locale.US);
 800        }
 801
 802        public boolean ranks(Role role) {
 803            return rank >= role.rank;
 804        }
 805    }
 806
 807    public enum Error {
 808        NO_RESPONSE,
 809        SERVER_NOT_FOUND,
 810        REMOTE_SERVER_TIMEOUT,
 811        NONE,
 812        NICK_IN_USE,
 813        PASSWORD_REQUIRED,
 814        BANNED,
 815        MEMBERS_ONLY,
 816        RESOURCE_CONSTRAINT,
 817        KICKED,
 818        SHUTDOWN,
 819        DESTROYED,
 820        INVALID_NICK,
 821        TECHNICAL_PROBLEMS,
 822        UNKNOWN,
 823        NON_ANONYMOUS
 824    }
 825
 826    private interface OnEventListener {
 827        void onSuccess();
 828
 829        void onFailure();
 830    }
 831
 832    public interface OnRenameListener extends OnEventListener {}
 833
 834    public static class User implements Comparable<User>, AvatarService.Avatarable {
 835        private Role role = Role.NONE;
 836        private Affiliation affiliation = Affiliation.NONE;
 837        private Jid realJid;
 838        private Jid fullJid;
 839        private long pgpKeyId = 0;
 840        private Avatar avatar;
 841        private final MucOptions options;
 842        private ChatState chatState = Config.DEFAULT_CHAT_STATE;
 843        private String occupantId;
 844
 845        public User(MucOptions options, Jid fullJid) {
 846            this.options = options;
 847            this.fullJid = fullJid;
 848        }
 849
 850        public String getName() {
 851            return fullJid == null ? null : fullJid.getResource();
 852        }
 853
 854        public Role getRole() {
 855            return this.role;
 856        }
 857
 858        public void setRole(String role) {
 859            this.role = Role.of(role);
 860        }
 861
 862        public Affiliation getAffiliation() {
 863            return this.affiliation;
 864        }
 865
 866        public void setAffiliation(String affiliation) {
 867            this.affiliation = Affiliation.of(affiliation);
 868        }
 869
 870        public long getPgpKeyId() {
 871            if (this.pgpKeyId != 0) {
 872                return this.pgpKeyId;
 873            } else if (realJid != null) {
 874                return getAccount().getRoster().getContact(realJid).getPgpKeyId();
 875            } else {
 876                return 0;
 877            }
 878        }
 879
 880        public void setPgpKeyId(long id) {
 881            this.pgpKeyId = id;
 882        }
 883
 884        public Contact getContact() {
 885            if (fullJid != null) {
 886                return getAccount().getRoster().getContactFromContactList(realJid);
 887            } else if (realJid != null) {
 888                return getAccount().getRoster().getContact(realJid);
 889            } else {
 890                return null;
 891            }
 892        }
 893
 894        public boolean setAvatar(final Avatar avatar) {
 895            if (this.avatar != null && this.avatar.equals(avatar)) {
 896                return false;
 897            } else {
 898                this.avatar = avatar;
 899                return true;
 900            }
 901        }
 902
 903        public String getAvatar() {
 904            if (avatar != null) {
 905                return avatar.getFilename();
 906            }
 907            Avatar avatar =
 908                    realJid != null
 909                            ? getAccount().getRoster().getContact(realJid).getAvatar()
 910                            : null;
 911            return avatar == null ? null : avatar.getFilename();
 912        }
 913
 914        public Account getAccount() {
 915            return options.getAccount();
 916        }
 917
 918        public Conversation getConversation() {
 919            return options.getConversation();
 920        }
 921
 922        public Jid getFullJid() {
 923            return fullJid;
 924        }
 925
 926        @Override
 927        public boolean equals(Object o) {
 928            if (this == o) return true;
 929            if (o == null || getClass() != o.getClass()) return false;
 930
 931            User user = (User) o;
 932
 933            if (role != user.role) return false;
 934            if (affiliation != user.affiliation) return false;
 935            if (!Objects.equals(realJid, user.realJid)) return false;
 936            return Objects.equals(fullJid, user.fullJid);
 937        }
 938
 939        public boolean isDomain() {
 940            return realJid != null && realJid.getLocal() == null && role == Role.NONE;
 941        }
 942
 943        @Override
 944        public int hashCode() {
 945            int result = role != null ? role.hashCode() : 0;
 946            result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0);
 947            result = 31 * result + (realJid != null ? realJid.hashCode() : 0);
 948            result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0);
 949            return result;
 950        }
 951
 952        @Override
 953        public String toString() {
 954            return "[fulljid:"
 955                    + fullJid
 956                    + ",realjid:"
 957                    + realJid
 958                    + ",affiliation"
 959                    + affiliation.toString()
 960                    + "]";
 961        }
 962
 963        public boolean realJidMatchesAccount() {
 964            return realJid != null && realJid.equals(options.account.getJid().asBareJid());
 965        }
 966
 967        @Override
 968        public int compareTo(@NonNull User another) {
 969            if (another.getAffiliation().outranks(getAffiliation())) {
 970                return 1;
 971            } else if (getAffiliation().outranks(another.getAffiliation())) {
 972                return -1;
 973            } else {
 974                return getComparableName().compareToIgnoreCase(another.getComparableName());
 975            }
 976        }
 977
 978        public String getComparableName() {
 979            Contact contact = getContact();
 980            if (contact != null) {
 981                return contact.getDisplayName();
 982            } else {
 983                String name = getName();
 984                return name == null ? "" : name;
 985            }
 986        }
 987
 988        public Jid getRealJid() {
 989            return realJid;
 990        }
 991
 992        public void setRealJid(Jid jid) {
 993            this.realJid = jid != null ? jid.asBareJid() : null;
 994        }
 995
 996        public boolean setChatState(ChatState chatState) {
 997            if (this.chatState == chatState) {
 998                return false;
 999            }
1000            this.chatState = chatState;
1001            return true;
1002        }
1003
1004        @Override
1005        public int getAvatarBackgroundColor() {
1006            final String seed = realJid != null ? realJid.asBareJid().toString() : null;
1007            return UIHelper.getColorForName(seed == null ? getName() : seed);
1008        }
1009
1010        @Override
1011        public String getAvatarName() {
1012            return getConversation().getName().toString();
1013        }
1014
1015        public void setOccupantId(final String occupantId) {
1016            this.occupantId = occupantId;
1017        }
1018
1019        public String getOccupantId() {
1020            return this.occupantId;
1021        }
1022    }
1023}