diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 7506ad800..66c457b8c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -1041,7 +1041,7 @@ final class GroupManagerV2 { throw new GroupChangeFailedException(e); } catch (AuthorizationFailedException e) { Log.w(TAG, e); - throw new GroupLinkNotActiveException(e); + throw new GroupLinkNotActiveException(e, Optional.absent()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/FetchGroupDetailsError.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/FetchGroupDetailsError.java index 9ee2281fb..180b48994 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/FetchGroupDetailsError.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/FetchGroupDetailsError.java @@ -2,5 +2,6 @@ package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining; enum FetchGroupDetailsError { GroupLinkNotActive, + BannedFromGroup, NetworkError } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java index 93c374ced..f2e08d4ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java @@ -127,10 +127,7 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF viewModel.isBusy().observe(getViewLifecycleOwner(), isBusy -> busy.setVisibility(isBusy ? View.VISIBLE : View.GONE)); - viewModel.getErrors().observe(getViewLifecycleOwner(), error -> { - Toast.makeText(requireContext(), errorToMessage(error), Toast.LENGTH_SHORT).show(); - dismiss(); - }); + viewModel.getErrors().observe(getViewLifecycleOwner(), this::showError); viewModel.getJoinErrors().observe(getViewLifecycleOwner(), error -> Toast.makeText(requireContext(), errorToMessage(error), Toast.LENGTH_SHORT).show()); @@ -156,16 +153,34 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF () -> GroupDescriptionDialog.show(getChildFragmentManager(), name, description, true)); } - private @NonNull String errorToMessage(@NonNull FetchGroupDetailsError error) { - if (error == FetchGroupDetailsError.GroupLinkNotActive) { - return getString(R.string.GroupJoinBottomSheetDialogFragment_this_group_link_is_not_active); + private void showError(FetchGroupDetailsError error) { + avatar.setVisibility(View.INVISIBLE); + groupCancelButton.setVisibility(View.GONE); + groupDetails.setVisibility(View.VISIBLE); + groupJoinButton.setVisibility(View.VISIBLE); + groupJoinButton.setText(getString(android.R.string.ok)); + groupJoinButton.setOnClickListener(v -> dismissAllowingStateLoss()); + + switch (error) { + case GroupLinkNotActive: + groupName.setText(R.string.GroupJoinBottomSheetDialogFragment_cant_join_group); + groupDetails.setText(R.string.GroupJoinBottomSheetDialogFragment_this_group_link_is_no_longer_valid); + break; + case BannedFromGroup: + groupName.setText(R.string.GroupJoinBottomSheetDialogFragment_cant_join_group); + groupDetails.setText(R.string.GroupJoinBottomSheetDialogFragment_you_cant_join_this_group_via_the_group_link_because_an_admin_removed_you); + break; + case NetworkError: + groupName.setText(R.string.GroupJoinBottomSheetDialogFragment_link_error); + groupDetails.setText(R.string.GroupJoinBottomSheetDialogFragment_joining_via_this_link_failed_try_joining_again_later); + break; } - return getString(R.string.GroupJoinBottomSheetDialogFragment_unable_to_get_group_information_please_try_again_later); } private @NonNull String errorToMessage(@NonNull JoinGroupError error) { switch (error) { case GROUP_LINK_NOT_ACTIVE: return getString(R.string.GroupJoinBottomSheetDialogFragment_this_group_link_is_not_active); + case BANNED : return getString(R.string.GroupJoinBottomSheetDialogFragment_you_cant_join_this_group_via_the_group_link_because_an_admin_removed_you); case NETWORK_ERROR : return getString(R.string.GroupJoinBottomSheetDialogFragment_encountered_a_network_error); default : return getString(R.string.GroupJoinBottomSheetDialogFragment_unable_to_join_group_please_try_again_later); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinRepository.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinRepository.java index 99257e154..528a87ee1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinRepository.java @@ -39,7 +39,9 @@ final class GroupJoinRepository { callback.onComplete(getGroupDetails()); } catch (IOException e) { callback.onError(FetchGroupDetailsError.NetworkError); - } catch (VerificationFailedException | GroupLinkNotActiveException e) { + } catch (GroupLinkNotActiveException e) { + callback.onError(e.getReason() == GroupLinkNotActiveException.Reason.BANNED ? FetchGroupDetailsError.BannedFromGroup : FetchGroupDetailsError.GroupLinkNotActive); + } catch (VerificationFailedException e) { callback.onError(FetchGroupDetailsError.GroupLinkNotActive); } }); @@ -62,7 +64,7 @@ final class GroupJoinRepository { } catch (GroupChangeBusyException e) { callback.onError(JoinGroupError.BUSY); } catch (GroupLinkNotActiveException e) { - callback.onError(JoinGroupError.GROUP_LINK_NOT_ACTIVE); + callback.onError(e.getReason() == GroupLinkNotActiveException.Reason.BANNED ? JoinGroupError.BANNED : JoinGroupError.GROUP_LINK_NOT_ACTIVE); } catch (GroupChangeFailedException | MembershipNotSuitableForV2Exception e) { callback.onError(JoinGroupError.FAILED); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/JoinGroupError.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/JoinGroupError.java index 059b2ce2b..21586bd55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/JoinGroupError.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/JoinGroupError.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining; enum JoinGroupError { BUSY, GROUP_LINK_NOT_ACTIVE, + BANNED, FAILED, NETWORK_ERROR, } diff --git a/app/src/main/res/layout/group_join_bottom_sheet.xml b/app/src/main/res/layout/group_join_bottom_sheet.xml index e47caa84a..ae39b9ea5 100644 --- a/app/src/main/res/layout/group_join_bottom_sheet.xml +++ b/app/src/main/res/layout/group_join_bottom_sheet.xml @@ -44,8 +44,11 @@ Unable to join group. Please try again later Encountered a network error. This group link is not active + + Can\'t join group + + You can\'t join this group via the group link because an admin removed you. + + This group link is no longer valid. + + Link error + + Joining via this link failed. Try joining again later. - Unable to get group information, please try again later Do you want to join this group and share your name and photo with its members? An admin of this group must approve your request before you can join this group. When you request to join, your name and photo will be shared with its members. diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupLinkNotActiveException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupLinkNotActiveException.java index 3a4a197fc..c5973465e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupLinkNotActiveException.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupLinkNotActiveException.java @@ -1,16 +1,34 @@ package org.whispersystems.signalservice.api.groupsv2; +import org.whispersystems.libsignal.util.guava.Optional; + /** * Thrown when a group link: * - has an out of date password, or; * - is currently not shared, or; + * - has been banned from the group, or; * - the master key does not match a group on the server */ public final class GroupLinkNotActiveException extends Exception { - public GroupLinkNotActiveException() { + + private final Reason reason; + + public GroupLinkNotActiveException(Throwable t, Optional reason) { + super(t); + + if (reason.isPresent() && reason.get().equalsIgnoreCase("banned")) { + this.reason = Reason.BANNED; + } else { + this.reason = Reason.UNKNOWN; + } } - public GroupLinkNotActiveException(Throwable t) { - super(t); + public Reason getReason() { + return reason; + } + + public enum Reason { + UNKNOWN, + BANNED } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java index a9a046f75..2305ace92 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java @@ -134,7 +134,7 @@ public class GroupsV2Api { return groupOperations.decryptGroupJoinInfo(joinInfo); } catch (ForbiddenException e) { - throw new GroupLinkNotActiveException(); + throw new GroupLinkNotActiveException(null, e.getReason()); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 8285c8d2e..1ffa69f36 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -36,6 +36,7 @@ import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.libsignal.util.guava.Function; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest; @@ -1968,7 +1969,7 @@ public class PushServiceSocket { ResponseBody responseBody = response.body(); try { - responseCodeHandler.handle(response.code(), responseBody); + responseCodeHandler.handle(response.code(), responseBody, response::header); switch (response.code()) { case 204: @@ -2293,6 +2294,10 @@ public class PushServiceSocket { private interface ResponseCodeHandler { void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException; + + default void handle(int responseCode, ResponseBody body, Function getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException { + handle(responseCode, body); + } } private static class EmptyResponseCodeHandler implements ResponseCodeHandler { @@ -2328,8 +2333,18 @@ public class PushServiceSocket { private static final ResponseCodeHandler GROUPS_V2_PATCH_RESPONSE_HANDLER = (responseCode, body) -> { if (responseCode == 400) throw new GroupPatchNotAcceptedException(); }; - private static final ResponseCodeHandler GROUPS_V2_GET_JOIN_INFO_HANDLER = (responseCode, body) -> { - if (responseCode == 403) throw new ForbiddenException(); + private static final ResponseCodeHandler GROUPS_V2_GET_JOIN_INFO_HANDLER = new ResponseCodeHandler() { + @Override + public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException { + if (responseCode == 403) throw new ForbiddenException(); + } + + @Override + public void handle(int responseCode, ResponseBody body, Function getHeader) throws NonSuccessfulResponseCodeException { + if (responseCode == 403) { + throw new ForbiddenException(Optional.fromNullable(getHeader.apply("X-Signal-Forbidden-Reason"))); + } + } }; public void putNewGroupsV2Group(Group group, GroupsV2AuthorizationString authorization) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/exceptions/ForbiddenException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/exceptions/ForbiddenException.java index 99407f7f4..9e8006c3f 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/exceptions/ForbiddenException.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/exceptions/ForbiddenException.java @@ -1,9 +1,21 @@ package org.whispersystems.signalservice.internal.push.exceptions; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; public final class ForbiddenException extends NonSuccessfulResponseCodeException { + private Optional reason; + public ForbiddenException() { + this(Optional.absent()); + } + + public ForbiddenException(Optional reason) { super(403); + this.reason = reason; + } + + public Optional getReason() { + return reason; } }