Merge pull request #2367 from nextcloud/feature/1783/implement-participant-permissions

Respect participant permissions
This commit is contained in:
Tim Krüger 2022-09-26 13:00:31 +02:00 committed by GitHub
commit e8c9c6a200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 407 additions and 292 deletions

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Mario Danic * @author Mario Danic
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -90,7 +92,6 @@ import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.NotificationUtils; import com.nextcloud.talk.utils.NotificationUtils;
import com.nextcloud.talk.utils.animations.PulseAnimation; import com.nextcloud.talk.utils.animations.PulseAnimation;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil; import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil;
import com.nextcloud.talk.utils.power.PowerManagerUtils; import com.nextcloud.talk.utils.power.PowerManagerUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences; import com.nextcloud.talk.utils.preferences.AppPreferences;
@ -163,6 +164,17 @@ import okhttp3.Cache;
import pub.devrel.easypermissions.AfterPermissionGranted; import pub.devrel.easypermissions.AfterPermissionGranted;
import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MODIFIED_BASE_URL;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY;
import static com.nextcloud.talk.webrtc.Globals.JOB_ID; import static com.nextcloud.talk.webrtc.Globals.JOB_ID;
import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE; import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE;
import static com.nextcloud.talk.webrtc.Globals.ROOM_TOKEN; import static com.nextcloud.talk.webrtc.Globals.ROOM_TOKEN;
@ -246,7 +258,7 @@ public class CallActivity extends CallBaseActivity {
private Handler cameraSwitchHandler = new Handler(); private Handler cameraSwitchHandler = new Handler();
// push to talk // push to talk
private boolean isPTTActive = false; private boolean isPushToTalkActive = false;
private PulseAnimation pulseAnimation; private PulseAnimation pulseAnimation;
private String baseUrl; private String baseUrl;
@ -283,6 +295,9 @@ public class CallActivity extends CallBaseActivity {
} }
}); });
private boolean canPublishAudioStream;
private boolean canPublishVideoStream;
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -297,21 +312,23 @@ public class CallActivity extends CallBaseActivity {
hideNavigationIfNoPipAvailable(); hideNavigationIfNoPipAvailable();
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
roomId = extras.getString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), ""); roomId = extras.getString(KEY_ROOM_ID, "");
roomToken = extras.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), ""); roomToken = extras.getString(KEY_ROOM_TOKEN, "");
conversationUser = extras.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()); conversationUser = extras.getParcelable(KEY_USER_ENTITY);
conversationPassword = extras.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), ""); conversationPassword = extras.getString(KEY_CONVERSATION_PASSWORD, "");
conversationName = extras.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), ""); conversationName = extras.getString(KEY_CONVERSATION_NAME, "");
isVoiceOnlyCall = extras.getBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false); isVoiceOnlyCall = extras.getBoolean(KEY_CALL_VOICE_ONLY, false);
isCallWithoutNotification = extras.getBoolean(BundleKeys.INSTANCE.getKEY_CALL_WITHOUT_NOTIFICATION(), false); isCallWithoutNotification = extras.getBoolean(KEY_CALL_WITHOUT_NOTIFICATION, false);
canPublishAudioStream = extras.getBoolean(KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO);
canPublishVideoStream = extras.getBoolean(KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO);
if (extras.containsKey(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL())) { if (extras.containsKey(KEY_FROM_NOTIFICATION_START_CALL)) {
isIncomingCallFromNotification = extras.getBoolean(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL()); isIncomingCallFromNotification = extras.getBoolean(KEY_FROM_NOTIFICATION_START_CALL);
} }
credentials = ApiUtils.getCredentials(conversationUser.getUsername(), conversationUser.getToken()); credentials = ApiUtils.getCredentials(conversationUser.getUsername(), conversationUser.getToken());
baseUrl = extras.getString(BundleKeys.INSTANCE.getKEY_MODIFIED_BASE_URL(), ""); baseUrl = extras.getString(KEY_MODIFIED_BASE_URL, "");
if (TextUtils.isEmpty(baseUrl)) { if (TextUtils.isEmpty(baseUrl)) {
baseUrl = conversationUser.getBaseUrl(); baseUrl = conversationUser.getBaseUrl();
} }
@ -377,23 +394,41 @@ public class CallActivity extends CallBaseActivity {
audioOutputDialog.show(); audioOutputDialog.show();
}); });
binding.microphoneButton.setOnClickListener(l -> onMicrophoneClick()); if (canPublishAudioStream) {
binding.microphoneButton.setOnLongClickListener(l -> { binding.microphoneButton.setOnClickListener(l -> onMicrophoneClick());
if (!microphoneOn) { binding.microphoneButton.setOnLongClickListener(l -> {
callControlHandler.removeCallbacksAndMessages(null); if (!microphoneOn) {
callInfosHandler.removeCallbacksAndMessages(null); callControlHandler.removeCallbacksAndMessages(null);
cameraSwitchHandler.removeCallbacksAndMessages(null); callInfosHandler.removeCallbacksAndMessages(null);
isPTTActive = true; cameraSwitchHandler.removeCallbacksAndMessages(null);
binding.callControls.setVisibility(View.VISIBLE); isPushToTalkActive = true;
if (!isVoiceOnlyCall) { binding.callControls.setVisibility(View.VISIBLE);
binding.switchSelfVideoButton.setVisibility(View.VISIBLE); if (!isVoiceOnlyCall) {
binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
}
} }
} onMicrophoneClick();
onMicrophoneClick(); return true;
return true; });
}); } else {
binding.microphoneButton.setOnClickListener(
l -> Toast.makeText(context,
R.string.nc_not_allowed_to_activate_audio,
Toast.LENGTH_SHORT
).show()
);
}
binding.cameraButton.setOnClickListener(l -> onCameraClick()); if (canPublishVideoStream) {
binding.cameraButton.setOnClickListener(l -> onCameraClick());
} else {
binding.cameraButton.setOnClickListener(
l -> Toast.makeText(context,
R.string.nc_not_allowed_to_activate_video,
Toast.LENGTH_SHORT
).show()
);
}
binding.hangupButton.setOnClickListener(l -> { binding.hangupButton.setOnClickListener(l -> {
hangup(true); hangup(true);
@ -548,7 +583,7 @@ public class CallActivity extends CallBaseActivity {
} }
} }
checkPermissions(); checkDevicePermissions();
} }
@Override @Override
@ -695,7 +730,7 @@ public class CallActivity extends CallBaseActivity {
} }
private void checkPermissions() { private void checkDevicePermissions() {
if (isVoiceOnlyCall) { if (isVoiceOnlyCall) {
onMicrophoneClick(); onMicrophoneClick();
} else { } else {
@ -754,7 +789,7 @@ public class CallActivity extends CallBaseActivity {
binding.switchSelfVideoButton.setVisibility(View.VISIBLE); binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
} }
if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) { if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) && canPublishVideoStream) {
if (!videoOn) { if (!videoOn) {
onCameraClick(); onCameraClick();
} }
@ -765,7 +800,7 @@ public class CallActivity extends CallBaseActivity {
} }
} }
if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) { if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE) && canPublishAudioStream) {
if (!microphoneOn) { if (!microphoneOn) {
onMicrophoneClick(); onMicrophoneClick();
} }
@ -878,6 +913,22 @@ public class CallActivity extends CallBaseActivity {
} }
public void onMicrophoneClick() { public void onMicrophoneClick() {
if (!canPublishAudioStream) {
microphoneOn = false;
binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px);
toggleMedia(false, false);
}
if (isVoiceOnlyCall && !isConnectionEstablished()) {
fetchSignalingSettings();
}
if (!canPublishAudioStream) {
// In the case no audio stream will be published it's not needed to check microphone permissions
return;
}
if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) { if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
if (!appPreferences.getPushToTalkIntroShown()) { if (!appPreferences.getPushToTalkIntroShown()) {
@ -905,7 +956,7 @@ public class CallActivity extends CallBaseActivity {
appPreferences.setPushToTalkIntroShown(true); appPreferences.setPushToTalkIntroShown(true);
} }
if (!isPTTActive) { if (!isPushToTalkActive) {
microphoneOn = !microphoneOn; microphoneOn = !microphoneOn;
if (microphoneOn) { if (microphoneOn) {
@ -926,11 +977,6 @@ public class CallActivity extends CallBaseActivity {
pulseAnimation.start(); pulseAnimation.start();
toggleMedia(true, false); toggleMedia(true, false);
} }
if (isVoiceOnlyCall && !isConnectionEstablished()) {
fetchSignalingSettings();
}
} else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_MICROPHONE)) { } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_MICROPHONE)) {
// Microphone permission is permanently denied so we cannot request it normally. // Microphone permission is permanently denied so we cannot request it normally.
@ -947,6 +993,14 @@ public class CallActivity extends CallBaseActivity {
} }
public void onCameraClick() { public void onCameraClick() {
if (!canPublishVideoStream) {
videoOn = false;
binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px);
binding.switchSelfVideoButton.setVisibility(View.GONE);
return;
}
if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) { if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
videoOn = !videoOn; videoOn = !videoOn;
@ -1057,7 +1111,7 @@ public class CallActivity extends CallBaseActivity {
if (spotlightView != null && spotlightView.getVisibility() != View.GONE) { if (spotlightView != null && spotlightView.getVisibility() != View.GONE) {
spotlightView.setVisibility(View.GONE); spotlightView.setVisibility(View.GONE);
} }
} else if (!isPTTActive) { } else if (!isPushToTalkActive) {
float alpha; float alpha;
long duration; long duration;
@ -1106,7 +1160,7 @@ public class CallActivity extends CallBaseActivity {
callControlHandler.postDelayed(new Runnable() { callControlHandler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (!isPTTActive) { if (!isPushToTalkActive) {
animateCallControls(false, 0); animateCallControls(false, 0);
} }
} }
@ -1133,7 +1187,7 @@ public class CallActivity extends CallBaseActivity {
callInfosHandler.postDelayed(new Runnable() { callInfosHandler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (!isPTTActive) { if (!isPushToTalkActive) {
animateCallControls(false, 0); animateCallControls(false, 0);
} }
} }
@ -1374,12 +1428,14 @@ public class CallActivity extends CallBaseActivity {
} }
private void performCall() { private void performCall() {
int inCallFlag; int inCallFlag = Participant.InCallFlags.IN_CALL;
if (isVoiceOnlyCall) {
inCallFlag = Participant.InCallFlags.IN_CALL + Participant.InCallFlags.WITH_AUDIO; if (canPublishAudioStream) {
} else { inCallFlag += Participant.InCallFlags.WITH_AUDIO;
inCallFlag = }
Participant.InCallFlags.IN_CALL + Participant.InCallFlags.WITH_AUDIO + Participant.InCallFlags.WITH_VIDEO;
if (!isVoiceOnlyCall && canPublishVideoStream) {
inCallFlag += Participant.InCallFlags.WITH_VIDEO;
} }
int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1}); int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
@ -1485,7 +1541,7 @@ public class CallActivity extends CallBaseActivity {
private void initiateCall() { private void initiateCall() {
if (!TextUtils.isEmpty(roomToken)) { if (!TextUtils.isEmpty(roomToken)) {
checkPermissions(); checkDevicePermissions();
} else { } else {
handleFromNotification(); handleFromNotification();
} }
@ -2292,7 +2348,7 @@ public class CallActivity extends CallBaseActivity {
if (peerConnectionWrapper != null) { if (peerConnectionWrapper != null) {
PeerConnection.IceConnectionState iceConnectionState = peerConnectionWrapper.getPeerConnection().iceConnectionState(); PeerConnection.IceConnectionState iceConnectionState = peerConnectionWrapper.getPeerConnection().iceConnectionState();
connected = iceConnectionState == PeerConnection.IceConnectionState.CONNECTED || connected = iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED; iceConnectionState == PeerConnection.IceConnectionState.COMPLETED;
} }
String nick; String nick;
@ -2471,7 +2527,7 @@ public class CallActivity extends CallBaseActivity {
binding.callInfosLinearLayout.setVisibility(View.GONE); binding.callInfosLinearLayout.setVisibility(View.GONE);
} }
if (!isPTTActive) { if (!isPushToTalkActive) {
animateCallControls(false, 5000); animateCallControls(false, 5000);
} }
@ -2590,8 +2646,8 @@ public class CallActivity extends CallBaseActivity {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
v.onTouchEvent(event); v.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isPTTActive) { if (event.getAction() == MotionEvent.ACTION_UP && isPushToTalkActive) {
isPTTActive = false; isPushToTalkActive = false;
binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px);
pulseAnimation.stop(); pulseAnimation.stop();
toggleMedia(false, false); toggleMedia(false, false);

View File

@ -129,9 +129,9 @@ public class CallNotificationActivity extends CallBaseActivity {
eventBus.post(new CallNotificationClick()); eventBus.post(new CallNotificationClick());
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
this.roomId = extras.getString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), ""); this.roomId = extras.getString(BundleKeys.KEY_ROOM_ID, "");
this.currentConversation = Parcels.unwrap(extras.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM())); this.currentConversation = Parcels.unwrap(extras.getParcelable(BundleKeys.KEY_ROOM));
this.userBeingCalled = extras.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()); this.userBeingCalled = extras.getParcelable(BundleKeys.KEY_USER_ENTITY);
this.originalBundle = extras; this.originalBundle = extras;
credentials = ApiUtils.getCredentials(userBeingCalled.getUsername(), userBeingCalled.getToken()); credentials = ApiUtils.getCredentials(userBeingCalled.getUsername(), userBeingCalled.getToken());
@ -169,13 +169,13 @@ public class CallNotificationActivity extends CallBaseActivity {
private void initClickListeners() { private void initClickListeners() {
binding.callAnswerVoiceOnlyView.setOnClickListener(l -> { binding.callAnswerVoiceOnlyView.setOnClickListener(l -> {
Log.d(TAG, "accept call (voice only)"); Log.d(TAG, "accept call (voice only)");
originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), true); originalBundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true);
proceedToCall(); proceedToCall();
}); });
binding.callAnswerCameraView.setOnClickListener(l -> { binding.callAnswerCameraView.setOnClickListener(l -> {
Log.d(TAG, "accept call (with video)"); Log.d(TAG, "accept call (with video)");
originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false); originalBundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, false);
proceedToCall(); proceedToCall();
}); });
@ -202,8 +202,8 @@ public class CallNotificationActivity extends CallBaseActivity {
} }
private void proceedToCall() { private void proceedToCall() {
originalBundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), currentConversation.getToken()); originalBundle.putString(BundleKeys.KEY_ROOM_TOKEN, currentConversation.getToken());
originalBundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), currentConversation.getDisplayName()); originalBundle.putString(BundleKeys.KEY_CONVERSATION_NAME, currentConversation.getDisplayName());
Intent intent = new Intent(this, CallActivity.class); Intent intent = new Intent(this, CallActivity.class);
intent.putExtras(originalBundle); intent.putExtras(originalBundle);

View File

@ -156,7 +156,6 @@ import com.nextcloud.talk.ui.dialog.ShowReactionsDialog
import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.AttendeePermissionsUtil
import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.ConductorRemapping.remapChatController import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
import com.nextcloud.talk.utils.ContactUtils import com.nextcloud.talk.utils.ContactUtils
@ -166,6 +165,7 @@ import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.ImageEmojiEditText import com.nextcloud.talk.utils.ImageEmojiEditText
import com.nextcloud.talk.utils.MagicCharPolicy import com.nextcloud.talk.utils.MagicCharPolicy
import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
@ -284,7 +284,7 @@ class ChatController(args: Bundle) :
lateinit var mediaPlayerHandler: Handler lateinit var mediaPlayerHandler: Handler
private var currentlyPlayedVoiceMessage: ChatMessage? = null private var currentlyPlayedVoiceMessage: ChatMessage? = null
var hasChatPermission: Boolean = false private lateinit var participantPermissions: ParticipantPermissions
private var videoURI: Uri? = null private var videoURI: Uri? = null
@ -306,6 +306,7 @@ class ChatController(args: Bundle) :
if (args.containsKey(KEY_ACTIVE_CONVERSATION)) { if (args.containsKey(KEY_ACTIVE_CONVERSATION)) {
this.currentConversation = Parcels.unwrap<Conversation>(args.getParcelable(KEY_ACTIVE_CONVERSATION)) this.currentConversation = Parcels.unwrap<Conversation>(args.getParcelable(KEY_ACTIVE_CONVERSATION))
this.participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
} }
this.roomPassword = args.getString(BundleKeys.KEY_CONVERSATION_PASSWORD, "") this.roomPassword = args.getString(BundleKeys.KEY_CONVERSATION_PASSWORD, "")
@ -353,11 +354,7 @@ class ChatController(args: Bundle) :
) )
loadAvatarForStatusBar() loadAvatarForStatusBar()
setTitle() setTitle()
participantPermissions = ParticipantPermissions(conversationUser, currentConversation!!)
hasChatPermission =
AttendeePermissionsUtil(currentConversation!!.permissions).hasChatPermission(
conversationUser
)
try { try {
setupSwipeToReply() setupSwipeToReply()
@ -395,7 +392,10 @@ class ChatController(args: Bundle) :
} }
private fun setupSwipeToReply() { private fun setupSwipeToReply() {
if (hasChatPermission && !isReadOnlyConversation()) { if (this::participantPermissions.isInitialized &&
participantPermissions.hasChatPermission() &&
!isReadOnlyConversation()
) {
val messageSwipeController = MessageSwipeCallback( val messageSwipeController = MessageSwipeCallback(
activity!!, activity!!,
object : MessageSwipeActions { object : MessageSwipeActions {
@ -434,6 +434,7 @@ class ChatController(args: Bundle) :
if (roomId == conversation.roomId) { if (roomId == conversation.roomId) {
roomToken = conversation.token roomToken = conversation.token
currentConversation = conversation currentConversation = conversation
participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
setTitle() setTitle()
getRoomInfo() getRoomInfo()
break break
@ -1261,7 +1262,7 @@ class ChatController(args: Bundle) :
if (isAlive()) { if (isAlive()) {
if (isReadOnlyConversation() || if (isReadOnlyConversation() ||
shouldShowLobby() || shouldShowLobby() ||
!hasChatPermission !participantPermissions.hasChatPermission()
) { ) {
binding.messageInputView.visibility = View.GONE binding.messageInputView.visibility = View.GONE
} else { } else {
@ -1272,7 +1273,9 @@ class ChatController(args: Bundle) :
private fun shouldShowLobby(): Boolean { private fun shouldShowLobby(): Boolean {
if (currentConversation != null) { if (currentConversation != null) {
return currentConversation?.shouldShowLobby(conversationUser!!) == true return currentConversation?.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
currentConversation?.canModerate(conversationUser!!) == false &&
!participantPermissions.canIgnoreLobby()
} }
return false return false
} }
@ -1311,14 +1314,14 @@ class ChatController(args: Bundle) :
private fun checkLobbyState() { private fun checkLobbyState() {
if (currentConversation != null && if (currentConversation != null &&
currentConversation?.isLobbyViewApplicable(conversationUser!!) ?: false && currentConversation?.isLobbyViewApplicable(conversationUser!!) == true &&
isAlive() isAlive()
) { ) {
if (!checkingLobbyStatus) { if (!checkingLobbyStatus) {
getRoomInfo() getRoomInfo()
} }
if (currentConversation?.shouldShowLobby(conversationUser!!) ?: false) { if (shouldShowLobby()) {
binding.lobby.lobbyView.visibility = View.VISIBLE binding.lobby.lobbyView.visibility = View.VISIBLE
binding.messagesListView.visibility = View.GONE binding.messagesListView.visibility = View.GONE
binding.messageInputView.visibility = View.GONE binding.messageInputView.visibility = View.GONE
@ -1610,8 +1613,8 @@ class ChatController(args: Bundle) :
private fun uploadFile(fileUri: String, isVoiceMessage: Boolean) { private fun uploadFile(fileUri: String, isVoiceMessage: Boolean) {
var metaData = "" var metaData = ""
if (!hasChatPermission) { if (!participantPermissions.hasChatPermission()) {
Log.w(TAG, "uploading file is forbidden because of missing attendee permissions") Log.w(TAG, "uploading file(s) is forbidden because of missing attendee permissions")
return return
} }
@ -2146,10 +2149,6 @@ class ChatController(args: Bundle) :
} }
pullChatMessagesPending = true pullChatMessagesPending = true
if (currentConversation != null && currentConversation!!.shouldShowLobby(conversationUser!!)) {
// return
}
val fieldMap = HashMap<String, Int>() val fieldMap = HashMap<String, Int>()
fieldMap["includeLastKnown"] = 0 fieldMap["includeLastKnown"] = 0
@ -2727,7 +2726,8 @@ class ChatController(args: Bundle) :
} }
private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) { private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) { val pp = ParticipantPermissions(conversationUser!!, currentConversation!!)
if (!pp.canStartCall() && currentConversation?.hasCall == false) {
Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
} else { } else {
ApplicationWideCurrentRoomHolder.getInstance().isDialing = true ApplicationWideCurrentRoomHolder.getInstance().isDialing = true
@ -2747,6 +2747,14 @@ class ChatController(args: Bundle) :
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword) bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl) bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl)
bundle.putString(KEY_CONVERSATION_NAME, it.displayName) bundle.putString(KEY_CONVERSATION_NAME, it.displayName)
bundle.putBoolean(
BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO,
participantPermissions.canPublishAudio()
)
bundle.putBoolean(
BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO,
participantPermissions.canPublishVideo()
)
if (isVoiceOnlyCall) { if (isVoiceOnlyCall) {
bundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true) bundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true)
@ -2774,7 +2782,7 @@ class ChatController(args: Bundle) :
currentConversation, currentConversation,
chatMessage, chatMessage,
conversationUser, conversationUser,
hasChatPermission, participantPermissions.hasChatPermission(),
ncApi ncApi
).show() ).show()
} }
@ -2802,7 +2810,7 @@ class ChatController(args: Bundle) :
conversationUser, conversationUser,
currentConversation, currentConversation,
isShowMessageDeletionButton(message), isShowMessageDeletionButton(message),
hasChatPermission, participantPermissions.hasChatPermission(),
ncApi ncApi
).show() ).show()
} }
@ -2814,7 +2822,7 @@ class ChatController(args: Bundle) :
} }
fun deleteMessage(message: IMessage?) { fun deleteMessage(message: IMessage?) {
if (!hasChatPermission) { if (!participantPermissions.hasChatPermission()) {
Log.w( Log.w(
TAG, TAG,
"Deletion of message is skipped because of restrictions by permissions. " + "Deletion of message is skipped because of restrictions by permissions. " +
@ -3144,7 +3152,7 @@ class ChatController(args: Bundle) :
message.hasFileAttachment() -> false message.hasFileAttachment() -> false
OBJECT_MESSAGE == message.message -> false OBJECT_MESSAGE == message.message -> false
!CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "delete-messages") -> false !CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "delete-messages") -> false
!hasChatPermission -> false !participantPermissions.hasChatPermission() -> false
else -> true else -> true
} }
} }

View File

@ -104,7 +104,7 @@ import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.AttendeePermissionsUtil import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.ConductorRemapping.remapChatController import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
@ -938,13 +938,11 @@ class ConversationsListController(bundle: Bundle) :
private fun handleConversation(conversation: Conversation?) { private fun handleConversation(conversation: Conversation?) {
selectedConversation = conversation selectedConversation = conversation
if (selectedConversation != null && activity != null) { if (selectedConversation != null && activity != null) {
val hasChatPermission = AttendeePermissionsUtil(selectedConversation!!.permissions).hasChatPermission( val hasChatPermission = ParticipantPermissions(currentUser!!, selectedConversation!!).hasChatPermission()
currentUser!!
)
if (showShareToScreen) { if (showShareToScreen) {
if (hasChatPermission && if (hasChatPermission &&
!isReadOnlyConversation(selectedConversation!!) && !isReadOnlyConversation(selectedConversation!!) &&
!selectedConversation!!.shouldShowLobby(currentUser!!) !shouldShowLobby(selectedConversation!!)
) { ) {
handleSharedData() handleSharedData()
} else { } else {
@ -963,6 +961,13 @@ class ConversationsListController(bundle: Bundle) :
} }
} }
private fun shouldShowLobby(conversation: Conversation): Boolean {
val participantPermissions = ParticipantPermissions(currentUser!!, conversation)
return conversation.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
!conversation.canModerate(currentUser!!) &&
!participantPermissions.canIgnoreLobby()
}
private fun isReadOnlyConversation(conversation: Conversation): Boolean { private fun isReadOnlyConversation(conversation: Conversation): Boolean {
return conversation.conversationReadOnlyState === return conversation.conversationReadOnlyState ===
Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY

View File

@ -63,18 +63,18 @@ public class AddParticipantsToConversation extends Worker {
@Override @Override
public Result doWork() { public Result doWork() {
Data data = getInputData(); Data data = getInputData();
String[] selectedUserIds = data.getStringArray(BundleKeys.INSTANCE.getKEY_SELECTED_USERS()); String[] selectedUserIds = data.getStringArray(BundleKeys.KEY_SELECTED_USERS);
String[] selectedGroupIds = data.getStringArray(BundleKeys.INSTANCE.getKEY_SELECTED_GROUPS()); String[] selectedGroupIds = data.getStringArray(BundleKeys.KEY_SELECTED_GROUPS);
String[] selectedCircleIds = data.getStringArray(BundleKeys.INSTANCE.getKEY_SELECTED_CIRCLES()); String[] selectedCircleIds = data.getStringArray(BundleKeys.KEY_SELECTED_CIRCLES);
String[] selectedEmails = data.getStringArray(BundleKeys.INSTANCE.getKEY_SELECTED_EMAILS()); String[] selectedEmails = data.getStringArray(BundleKeys.KEY_SELECTED_EMAILS);
User user = User user =
userManager.getUserWithInternalId( userManager.getUserWithInternalId(
data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), -1)) data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1))
.blockingGet(); .blockingGet();
int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.APIv4, 1}); int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.APIv4, 1});
String conversationToken = data.getString(BundleKeys.INSTANCE.getKEY_TOKEN()); String conversationToken = data.getString(BundleKeys.KEY_TOKEN);
String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken()); String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken());
RetrofitBucket retrofitBucket; RetrofitBucket retrofitBucket;

View File

@ -109,7 +109,7 @@ public class CapabilitiesWorker extends Worker {
Data data = getInputData(); Data data = getInputData();
long internalUserId = data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), -1); long internalUserId = data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1);
List<User> userEntityObjectList = new ArrayList<>(); List<User> userEntityObjectList = new ArrayList<>();
boolean userExists = userManager.getUserWithInternalId(internalUserId).isEmpty().blockingGet(); boolean userExists = userManager.getUserWithInternalId(internalUserId).isEmpty().blockingGet();

View File

@ -75,8 +75,8 @@ public class DeleteConversationWorker extends Worker {
@Override @Override
public Result doWork() { public Result doWork() {
Data data = getInputData(); Data data = getInputData();
long operationUserId = data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), -1); long operationUserId = data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1);
String conversationToken = data.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN()); String conversationToken = data.getString(BundleKeys.KEY_ROOM_TOKEN);
User operationUser = userManager.getUserWithId(operationUserId).blockingGet(); User operationUser = userManager.getUserWithId(operationUserId).blockingGet();
if (operationUser != null) { if (operationUser != null) {

View File

@ -79,8 +79,8 @@ public class LeaveConversationWorker extends Worker {
@Override @Override
public Result doWork() { public Result doWork() {
Data data = getInputData(); Data data = getInputData();
long operationUserId = data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), -1); long operationUserId = data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1);
String conversationToken = data.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN()); String conversationToken = data.getString(BundleKeys.KEY_ROOM_TOKEN);
User operationUser = userManager.getUserWithId(operationUserId).blockingGet(); User operationUser = userManager.getUserWithId(operationUserId).blockingGet();
if (operationUser != null) { if (operationUser != null) {

View File

@ -138,7 +138,7 @@ public class NotificationWorker extends Worker {
importantConversation = arbitraryStorageManager.getStorageSetting( importantConversation = arbitraryStorageManager.getStorageSetting(
UserIdUtils.INSTANCE.getIdForUser(user), UserIdUtils.INSTANCE.getIdForUser(user),
"important_conversation", "important_conversation",
intent.getExtras().getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN())) intent.getExtras().getString(BundleKeys.KEY_ROOM_TOKEN))
.map(arbitraryStorage -> { .map(arbitraryStorage -> {
if (arbitraryStorage != null && arbitraryStorage.getValue() != null) { if (arbitraryStorage != null && arbitraryStorage.getValue() != null) {
return Boolean.parseBoolean(arbitraryStorage.getValue()); return Boolean.parseBoolean(arbitraryStorage.getValue());
@ -154,7 +154,7 @@ public class NotificationWorker extends Worker {
int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.APIv4, 1}); int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.APIv4, 1});
ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, user.getBaseUrl(), ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, user.getBaseUrl(),
intent.getExtras().getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN()))) intent.getExtras().getString(BundleKeys.KEY_ROOM_TOKEN)))
.blockingSubscribe(new Observer<RoomOverall>() { .blockingSubscribe(new Observer<RoomOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -165,7 +165,7 @@ public class NotificationWorker extends Worker {
public void onNext(RoomOverall roomOverall) { public void onNext(RoomOverall roomOverall) {
Conversation conversation = roomOverall.getOcs().getData(); Conversation conversation = roomOverall.getOcs().getData();
intent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation)); intent.putExtra(BundleKeys.KEY_ROOM, Parcels.wrap(conversation));
if (conversation.getType().equals(Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) || if (conversation.getType().equals(Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) ||
(!TextUtils.isEmpty(conversation.getObjectType()) && "share:password".equals (!TextUtils.isEmpty(conversation.getObjectType()) && "share:password".equals
(conversation.getObjectType()))) { (conversation.getObjectType()))) {
@ -351,12 +351,12 @@ public class NotificationWorker extends Worker {
} }
Bundle notificationInfo = new Bundle(); Bundle notificationInfo = new Bundle();
notificationInfo.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), notificationInfo.putLong(BundleKeys.KEY_INTERNAL_USER_ID,
signatureVerification.getUser().getId()); signatureVerification.getUser().getId());
// could be an ID or a TOKEN // could be an ID or a TOKEN
notificationInfo.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), notificationInfo.putString(BundleKeys.KEY_ROOM_TOKEN,
decryptedPushMessage.getId()); decryptedPushMessage.getId());
notificationInfo.putLong(BundleKeys.INSTANCE.getKEY_NOTIFICATION_ID(), notificationInfo.putLong(BundleKeys.KEY_NOTIFICATION_ID,
decryptedPushMessage.getNotificationId()); decryptedPushMessage.getNotificationId());
notificationBuilder.setExtras(notificationInfo); notificationBuilder.setExtras(notificationInfo);
@ -442,10 +442,10 @@ public class NotificationWorker extends Worker {
// NOTE - systemNotificationId is an internal ID used on the device only. // NOTE - systemNotificationId is an internal ID used on the device only.
// It is NOT the same as the notification ID used in communication with the server. // It is NOT the same as the notification ID used in communication with the server.
actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_SYSTEM_NOTIFICATION_ID(), systemNotificationId); actualIntent.putExtra(BundleKeys.KEY_SYSTEM_NOTIFICATION_ID, systemNotificationId);
actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), actualIntent.putExtra(BundleKeys.KEY_INTERNAL_USER_ID,
Objects.requireNonNull(signatureVerification.getUser()).getId()); Objects.requireNonNull(signatureVerification.getUser()).getId());
actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), decryptedPushMessage.getId()); actualIntent.putExtra(BundleKeys.KEY_ROOM_TOKEN, decryptedPushMessage.getId());
actualIntent.putExtra(BundleKeys.KEY_MESSAGE_ID, messageId); actualIntent.putExtra(BundleKeys.KEY_MESSAGE_ID, messageId);
int intentFlag; int intentFlag;
@ -583,8 +583,8 @@ public class NotificationWorker extends Worker {
context = getApplicationContext(); context = getApplicationContext();
Data data = getInputData(); Data data = getInputData();
String subject = data.getString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SUBJECT()); String subject = data.getString(BundleKeys.KEY_NOTIFICATION_SUBJECT);
String signature = data.getString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SIGNATURE()); String signature = data.getString(BundleKeys.KEY_NOTIFICATION_SIGNATURE);
try { try {
byte[] base64DecodedSubject = Base64.decode(subject, Base64.DEFAULT); byte[] base64DecodedSubject = Base64.decode(subject, Base64.DEFAULT);
@ -643,13 +643,13 @@ public class NotificationWorker extends Worker {
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), decryptedPushMessage.getId()); bundle.putString(BundleKeys.KEY_ROOM_TOKEN, decryptedPushMessage.getId());
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), bundle.putParcelable(BundleKeys.KEY_USER_ENTITY,
signatureVerification.getUser()); signatureVerification.getUser());
bundle.putBoolean(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL(), bundle.putBoolean(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL,
startACall); startACall);
intent.putExtras(bundle); intent.putExtras(bundle);
@ -657,12 +657,12 @@ public class NotificationWorker extends Worker {
switch (decryptedPushMessage.getType()) { switch (decryptedPushMessage.getType()) {
case "call": case "call":
if (bundle.containsKey(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN())) { if (bundle.containsKey(BundleKeys.KEY_ROOM_TOKEN)) {
showNotificationForCallWithNoPing(intent); showNotificationForCallWithNoPing(intent);
} }
break; break;
case "room": case "room":
if (bundle.containsKey(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN())) { if (bundle.containsKey(BundleKeys.KEY_ROOM_TOKEN)) {
showNotificationWithObjectData(intent); showNotificationWithObjectData(intent);
} }
break; break;

View File

@ -74,7 +74,7 @@ public class SignalingSettingsWorker extends Worker {
Data data = getInputData(); Data data = getInputData();
long internalUserId = data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), -1); long internalUserId = data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1);
List<User> userEntityObjectList = new ArrayList<>(); List<User> userEntityObjectList = new ArrayList<>();
boolean userExists = userManager.getUserWithInternalId(internalUserId).isEmpty().blockingGet(); boolean userExists = userManager.getUserWithInternalId(internalUserId).isEmpty().blockingGet();

View File

@ -155,10 +155,6 @@ data class Conversation(
return isParticipantOwnerOrModerator && !isLockedOneToOne(conversationUser) return isParticipantOwnerOrModerator && !isLockedOneToOne(conversationUser)
} }
fun shouldShowLobby(conversationUser: User): Boolean {
return LobbyState.LOBBY_STATE_MODERATORS_ONLY == lobbyState && !canModerate(conversationUser)
}
fun isLobbyViewApplicable(conversationUser: User): Boolean { fun isLobbyViewApplicable(conversationUser: User): Boolean {
return !canModerate(conversationUser) && return !canModerate(conversationUser) &&
(type == ConversationType.ROOM_GROUP_CALL || type == ConversationType.ROOM_PUBLIC_CALL) (type == ConversationType.ROOM_GROUP_CALL || type == ConversationType.ROOM_PUBLIC_CALL)

View File

@ -1,72 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
/**
* see https://nextcloud-talk.readthedocs.io/en/latest/constants/#attendee-permissions
*/
class AttendeePermissionsUtil(flag: Int) {
var isDefault: Boolean = false
var isCustom: Boolean = false
var canStartCall: Boolean = false
var canJoinCall: Boolean = false
var canIgnoreLobby: Boolean = false
var canPublishAudio: Boolean = false
var canPublishVideo: Boolean = false
var canPublishScreen: Boolean = false
private var hasChatPermission: Boolean = false
init {
isDefault = (flag and DEFAULT) == DEFAULT
isCustom = (flag and CUSTOM) == CUSTOM
canStartCall = (flag and START_CALL) == START_CALL
canJoinCall = (flag and JOIN_CALL) == JOIN_CALL
canIgnoreLobby = (flag and CAN_IGNORE_LOBBY) == CAN_IGNORE_LOBBY
canPublishAudio = (flag and PUBLISH_AUDIO) == PUBLISH_AUDIO
canPublishVideo = (flag and PUBLISH_VIDEO) == PUBLISH_VIDEO
canPublishScreen = (flag and PUBLISH_SCREEN) == PUBLISH_SCREEN
hasChatPermission = (flag and CHAT) == CHAT
}
fun hasChatPermission(user: User): Boolean {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "chat-permission")) {
return hasChatPermission
}
// if capability is not available then the spreed version doesn't support to restrict this
return true
}
companion object {
val TAG = AttendeePermissionsUtil::class.simpleName
const val DEFAULT = 0
const val CUSTOM = 1
const val START_CALL = 2
const val JOIN_CALL = 4
const val CAN_IGNORE_LOBBY = 8
const val PUBLISH_AUDIO = 16
const val PUBLISH_VIDEO = 32
const val PUBLISH_SCREEN = 64
const val CHAT = 128
}
}

View File

@ -0,0 +1,107 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
/**
* see https://nextcloud-talk.readthedocs.io/en/latest/constants/#attendee-permissions
*/
class ParticipantPermissions(
private val user: User,
private val conversation: Conversation
) {
val isDefault = (conversation.permissions and DEFAULT) == DEFAULT
val isCustom = (conversation.permissions and CUSTOM) == CUSTOM
private val canStartCall = (conversation.permissions and START_CALL) == START_CALL
val canJoinCall = (conversation.permissions and JOIN_CALL) == JOIN_CALL
private val canIgnoreLobby = (conversation.permissions and CAN_IGNORE_LOBBY) == CAN_IGNORE_LOBBY
private val canPublishAudio = (conversation.permissions and PUBLISH_AUDIO) == PUBLISH_AUDIO
private val canPublishVideo = (conversation.permissions and PUBLISH_VIDEO) == PUBLISH_VIDEO
val canPublishScreen = (conversation.permissions and PUBLISH_SCREEN) == PUBLISH_SCREEN
private val hasChatPermission = (conversation.permissions and CHAT) == CHAT
private fun hasConversationPermissions(): Boolean {
return CapabilitiesUtilNew.hasSpreedFeatureCapability(
user,
"conversation-permissions"
)
}
fun canIgnoreLobby(): Boolean {
if (hasConversationPermissions()) {
return canIgnoreLobby
}
return false
}
fun canStartCall(): Boolean {
return if (hasConversationPermissions()) {
canStartCall
} else {
conversation.canStartCall
}
}
fun canPublishAudio(): Boolean {
return if (hasConversationPermissions()) {
canPublishAudio
} else {
true
}
}
fun canPublishVideo(): Boolean {
return if (hasConversationPermissions()) {
canPublishVideo
} else {
true
}
}
fun hasChatPermission(): Boolean {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "chat-permission")) {
return hasChatPermission
}
// if capability is not available then the spreed version doesn't support to restrict this
return true
}
companion object {
val TAG = ParticipantPermissions::class.simpleName
const val DEFAULT = 0
const val CUSTOM = 1
const val START_CALL = 2
const val JOIN_CALL = 4
const val CAN_IGNORE_LOBBY = 8
const val PUBLISH_AUDIO = 16
const val PUBLISH_VIDEO = 32
const val PUBLISH_SCREEN = 64
const val CHAT = 128
}
}

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Mario Danic * @author Mario Danic
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2017 Mario Danic * Copyright (C) 2017 Mario Danic
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -21,58 +23,58 @@
package com.nextcloud.talk.utils.bundle package com.nextcloud.talk.utils.bundle
object BundleKeys { object BundleKeys {
val KEY_SELECTED_USERS = "KEY_SELECTED_USERS" const val KEY_SELECTED_USERS = "KEY_SELECTED_USERS"
val KEY_SELECTED_GROUPS = "KEY_SELECTED_GROUPS" const val KEY_SELECTED_GROUPS = "KEY_SELECTED_GROUPS"
val KEY_SELECTED_CIRCLES = "KEY_SELECTED_CIRCLES" const val KEY_SELECTED_CIRCLES = "KEY_SELECTED_CIRCLES"
val KEY_SELECTED_EMAILS = "KEY_SELECTED_EMAILS" const val KEY_SELECTED_EMAILS = "KEY_SELECTED_EMAILS"
val KEY_USERNAME = "KEY_USERNAME" const val KEY_USERNAME = "KEY_USERNAME"
val KEY_TOKEN = "KEY_TOKEN" const val KEY_TOKEN = "KEY_TOKEN"
val KEY_BASE_URL = "KEY_BASE_URL" const val KEY_BASE_URL = "KEY_BASE_URL"
val KEY_IS_ACCOUNT_IMPORT = "KEY_IS_ACCOUNT_IMPORT" const val KEY_IS_ACCOUNT_IMPORT = "KEY_IS_ACCOUNT_IMPORT"
val KEY_ORIGINAL_PROTOCOL = "KEY_ORIGINAL_PROTOCOL" const val KEY_ORIGINAL_PROTOCOL = "KEY_ORIGINAL_PROTOCOL"
val KEY_ROOM = "KEY_CONVERSATION" const val KEY_ROOM = "KEY_CONVERSATION"
val KEY_OPERATION_CODE = "KEY_OPERATION_CODE" const val KEY_OPERATION_CODE = "KEY_OPERATION_CODE"
val KEY_MENU_TYPE = "KEY_MENU_TYPE" const val KEY_SHARE_INTENT = "KEY_SHARE_INTENT"
val KEY_SHARE_INTENT = "KEY_SHARE_INTENT" const val KEY_APP_ITEM_PACKAGE_NAME = "KEY_APP_ITEM_PACKAGE_NAME"
val KEY_APP_ITEM_PACKAGE_NAME = "KEY_APP_ITEM_PACKAGE_NAME" const val KEY_APP_ITEM_NAME = "KEY_APP_ITEM_NAME"
val KEY_APP_ITEM_NAME = "KEY_APP_ITEM_NAME" const val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD"
val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD" const val KEY_ROOM_TOKEN = "KEY_ROOM_TOKEN"
val KEY_ROOM_TOKEN = "KEY_ROOM_TOKEN" const val KEY_ROOM_ONE_TO_ONE = "KEY_ROOM_ONE_TO_ONE"
val KEY_ROOM_ONE_TO_ONE = "KEY_ROOM_ONE_TO_ONE" const val KEY_USER_ENTITY = "KEY_USER_ENTITY"
val KEY_USER_ENTITY = "KEY_USER_ENTITY" const val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION"
val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION" const val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS"
val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS" const val KEY_EXISTING_PARTICIPANTS = "KEY_EXISTING_PARTICIPANTS"
val KEY_EXISTING_PARTICIPANTS = "KEY_EXISTING_PARTICIPANTS" const val KEY_CALL_URL = "KEY_CALL_URL"
val KEY_CALL_URL = "KEY_CALL_URL" const val KEY_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL"
val KEY_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL" const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT"
val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT" const val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE"
val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE" const val KEY_INTERNAL_USER_ID = "KEY_INTERNAL_USER_ID"
val KEY_INTERNAL_USER_ID = "KEY_INTERNAL_USER_ID" const val KEY_CONVERSATION_TYPE = "KEY_CONVERSATION_TYPE"
val KEY_CONVERSATION_TYPE = "KEY_CONVERSATION_TYPE" const val KEY_INVITED_PARTICIPANTS = "KEY_INVITED_PARTICIPANTS"
val KEY_INVITED_PARTICIPANTS = "KEY_INVITED_PARTICIPANTS" const val KEY_INVITED_CIRCLE = "KEY_INVITED_CIRCLE"
val KEY_INVITED_CIRCLE = "KEY_INVITED_CIRCLE" const val KEY_INVITED_GROUP = "KEY_INVITED_GROUP"
val KEY_INVITED_GROUP = "KEY_INVITED_GROUP" const val KEY_INVITED_EMAIL = "KEY_INVITED_EMAIL"
val KEY_INVITED_EMAIL = "KEY_INVITED_EMAIL" const val KEY_CONVERSATION_NAME = "KEY_CONVERSATION_NAME"
val KEY_CONVERSATION_NAME = "KEY_CONVERSATION_NAME" const val KEY_CALL_VOICE_ONLY = "KEY_CALL_VOICE_ONLY"
val KEY_CALL_VOICE_ONLY = "KEY_CALL_VOICE_ONLY" const val KEY_CALL_WITHOUT_NOTIFICATION = "KEY_CALL_WITHOUT_NOTIFICATION"
val KEY_CALL_WITHOUT_NOTIFICATION = "KEY_CALL_WITHOUT_NOTIFICATION" const val KEY_ACTIVE_CONVERSATION = "KEY_ACTIVE_CONVERSATION"
val KEY_ACTIVE_CONVERSATION = "KEY_ACTIVE_CONVERSATION" const val KEY_SERVER_CAPABILITIES = "KEY_SERVER_CAPABILITIES"
val KEY_SERVER_CAPABILITIES = "KEY_SERVER_CAPABILITIES" const val KEY_FROM_NOTIFICATION_START_CALL = "KEY_FROM_NOTIFICATION_START_CALL"
val KEY_FROM_NOTIFICATION_START_CALL = "KEY_FROM_NOTIFICATION_START_CALL" const val KEY_ROOM_ID = "KEY_ROOM_ID"
val KEY_ROOM_ID = "KEY_ROOM_ID" const val KEY_ARE_CALL_SOUNDS = "KEY_ARE_CALL_SOUNDS"
val KEY_ARE_CALL_SOUNDS = "KEY_ARE_CALL_SOUNDS" const val KEY_FILE_PATHS = "KEY_FILE_PATHS"
val KEY_BROWSER_TYPE = "KEY_BROWSER_TYPE" const val KEY_ACCOUNT = "KEY_ACCOUNT"
val KEY_FILE_PATHS = "KEY_FILE_PATHS" const val KEY_FILE_ID = "KEY_FILE_ID"
val KEY_ACCOUNT = "KEY_ACCOUNT" const val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"
val KEY_FILE_ID = "KEY_FILE_ID" const val KEY_SHARED_TEXT = "KEY_SHARED_TEXT"
val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID" const val KEY_GEOCODING_QUERY = "KEY_GEOCODING_QUERY"
val KEY_SHARED_TEXT = "KEY_SHARED_TEXT" const val KEY_META_DATA = "KEY_META_DATA"
val KEY_GEOCODING_QUERY = "KEY_GEOCODING_QUERY" const val KEY_FORWARD_MSG_FLAG = "KEY_FORWARD_MSG_FLAG"
val KEY_META_DATA = "KEY_META_DATA" const val KEY_FORWARD_MSG_TEXT = "KEY_FORWARD_MSG_TEXT"
val KEY_FORWARD_MSG_FLAG = "KEY_FORWARD_MSG_FLAG" const val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM"
val KEY_FORWARD_MSG_TEXT = "KEY_FORWARD_MSG_TEXT" const val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID"
val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM"
val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID"
const val KEY_MESSAGE_ID = "KEY_MESSAGE_ID" const val KEY_MESSAGE_ID = "KEY_MESSAGE_ID"
const val KEY_MIME_TYPE_FILTER = "KEY_MIME_TYPE_FILTER" const val KEY_MIME_TYPE_FILTER = "KEY_MIME_TYPE_FILTER"
const val KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO = "KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO"
const val KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO = "KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO"
} }

View File

@ -244,8 +244,8 @@ public class MagicWebSocketInstance extends WebSocketListener {
shouldRefreshChat = (boolean) chatMap.get("refresh"); shouldRefreshChat = (boolean) chatMap.get("refresh");
if (shouldRefreshChat) { if (shouldRefreshChat) {
HashMap<String, String> refreshChatHashMap = new HashMap<>(); HashMap<String, String> refreshChatHashMap = new HashMap<>();
refreshChatHashMap.put(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), (String) messageHashMap.get("roomid")); refreshChatHashMap.put(BundleKeys.KEY_ROOM_TOKEN, (String) messageHashMap.get("roomid"));
refreshChatHashMap.put(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), Long.toString(conversationUser.getId())); refreshChatHashMap.put(BundleKeys.KEY_INTERNAL_USER_ID, Long.toString(conversationUser.getId()));
eventBus.post(new WebSocketCommunicationEvent("refreshChat", refreshChatHashMap)); eventBus.post(new WebSocketCommunicationEvent("refreshChat", refreshChatHashMap));
} }
} }

View File

@ -602,4 +602,7 @@
<string name="nc_expire_message_one_hour">1 hour</string> <string name="nc_expire_message_one_hour">1 hour</string>
<string name="nc_expire_messages_explanation">Chat messages can be expired after a certain time. Note: Files shared in chat will not be deleted for the owner, but will no longer be shared in the conversation.</string> <string name="nc_expire_messages_explanation">Chat messages can be expired after a certain time. Note: Files shared in chat will not be deleted for the owner, but will no longer be shared in the conversation.</string>
<string name="nc_not_allowed_to_activate_audio">You\'re not allowed to activate audio!</string>
<string name="nc_not_allowed_to_activate_video">You\'re not allowed to activate video!</string>
</resources> </resources>

View File

@ -1,47 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils
import junit.framework.TestCase
import org.junit.Test
class AttendeePermissionsUtilTest : TestCase() {
@Test
fun test_areFlagsSet() {
val attendeePermissionsUtil =
AttendeePermissionsUtil(
AttendeePermissionsUtil.PUBLISH_SCREEN or
AttendeePermissionsUtil.JOIN_CALL or
AttendeePermissionsUtil.DEFAULT
)
assert(attendeePermissionsUtil.canPublishScreen)
assert(attendeePermissionsUtil.canJoinCall)
assert(attendeePermissionsUtil.isDefault)
assertFalse(attendeePermissionsUtil.isCustom)
assertFalse(attendeePermissionsUtil.canStartCall)
assertFalse(attendeePermissionsUtil.canIgnoreLobby)
assertFalse(attendeePermissionsUtil.canPublishAudio)
assertFalse(attendeePermissionsUtil.canPublishVideo)
}
}

View File

@ -0,0 +1,57 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.Conversation
import junit.framework.TestCase
import org.junit.Test
class ParticipantPermissionsTest : TestCase() {
@Test
fun test_areFlagsSet() {
val user = User()
val conversation = Conversation()
conversation.permissions = ParticipantPermissions.PUBLISH_SCREEN or
ParticipantPermissions.JOIN_CALL or
ParticipantPermissions.DEFAULT
val attendeePermissions =
ParticipantPermissions(
user,
conversation
)
assert(attendeePermissions.canPublishScreen)
assert(attendeePermissions.canJoinCall)
assert(attendeePermissions.isDefault)
assertFalse(attendeePermissions.isCustom)
assertFalse(attendeePermissions.canStartCall())
assertFalse(attendeePermissions.canIgnoreLobby())
assertTrue(attendeePermissions.canPublishAudio())
assertTrue(attendeePermissions.canPublishVideo())
}
}