diff --git a/README.md b/README.md index 1a30b6055..a231fa454 100644 --- a/README.md +++ b/README.md @@ -84,5 +84,12 @@ commit automatically with `git commit -s`. You can also use git [aliases](https: like `git config --global alias.ci 'commit -s'`. Now you can commit with `git ci` and the commit will be signed. +## Credits + +### Ringtones + +- [Ringtones by Librem](https://soundcloud.com/feandesign/sets/librem-5-sounds) +- [Telefon-Freiton in Deutschland nach DTAG 1 TR 110-1, Kap. 8.3](https://commons.wikimedia.org/wiki/File:1TR110-1_Kap8.3_Freiton1.ogg) + [dcofile]: https://github.com/nextcloud/talk-android/blob/master/contribute/developer-certificate-of-origin [applyalicense]: https://github.com/nextcloud/talk-android/blob/master/contribute/HowToApplyALicense.md diff --git a/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt index 5075ebace..5421d24bd 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt @@ -40,9 +40,6 @@ import com.nextcloud.talk.controllers.CallNotificationController import com.nextcloud.talk.controllers.ChatController import com.nextcloud.talk.events.ConfigurationChangeEvent import com.nextcloud.talk.utils.bundle.BundleKeys -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY @AutoInjector(NextcloudTalkApplication::class) class MagicCallActivity : BaseActivity() { diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index fbf23c522..cc839685f 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -177,8 +177,10 @@ public interface NcApi { Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /call/callToken */ + @FormUrlEncoded @POST - Observable joinCall(@Nullable @Header("Authorization") String authorization, @Url String url); + Observable joinCall(@Nullable @Header("Authorization") String authorization, @Url String url, + @Field("flags") Integer inCall); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /call/callToken diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallController.java index 490c95178..116f98073 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallController.java @@ -46,6 +46,10 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + import com.bluelinelabs.logansquare.LoganSquare; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.interfaces.DraweeController; @@ -136,9 +140,6 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import autodagger.AutoInjector; import butterknife.BindView; import butterknife.OnClick; @@ -177,24 +178,31 @@ public class CallController extends BaseController { @BindView(R.id.pip_video_view) SurfaceViewRenderer pipVideoView; - @BindView(R.id.relative_layout) - RelativeLayout relativeLayout; + @BindView(R.id.controllerCallLayout) + RelativeLayout controllerCallLayout; @BindView(R.id.remote_renderers_layout) LinearLayout remoteRenderersLayout; - @BindView(R.id.callControlsRelativeLayout) - RelativeLayout callControls; + @BindView(R.id.callControlsLinearLayout) + LinearLayout callControls; @BindView(R.id.call_control_microphone) SimpleDraweeView microphoneControlButton; @BindView(R.id.call_control_camera) SimpleDraweeView cameraControlButton; @BindView(R.id.call_control_switch_camera) SimpleDraweeView cameraSwitchButton; - @BindView(R.id.connectingTextView) - TextView connectingTextView; + @BindView(R.id.callStateTextView) + TextView callStateTextView; - @BindView(R.id.connectingRelativeLayoutView) - RelativeLayout connectingView; + @BindView(R.id.callInfosLinearLayout) + LinearLayout callInfosLinearLayout; + @BindView(R.id.callVoiceOrVideoTextView) + TextView callVoiceOrVideoTextView; + @BindView(R.id.callConversationNameTextView) + TextView callConversationNameTextView; + + @BindView(R.id.callStateRelativeLayoutView) + RelativeLayout callStateView; @BindView(R.id.conversationRelativeLayoutView) RelativeLayout conversationView; @@ -202,7 +210,7 @@ public class CallController extends BaseController { @BindView(R.id.errorImageView) ImageView errorImageView; - @BindView(R.id.progress_bar) + @BindView(R.id.callStateProgressBar) ProgressBar progressBar; @Inject @@ -234,6 +242,7 @@ public class CallController extends BaseController { private CameraEnumerator cameraEnumerator; private String roomToken; private UserEntity conversationUser; + private String conversationName; private String callSession; private MediaStream localMediaStream; private String credentials; @@ -247,9 +256,12 @@ public class CallController extends BaseController { private boolean needsPing = true; private boolean isVoiceOnlyCall; + private boolean isIncomingCallFromNotification; private Handler callControlHandler = new Handler(); + private Handler callInfosHandler = new Handler(); private Handler cameraSwitchHandler = new Handler(); + // push to talk private boolean isPTTActive = false; private PulseAnimation pulseAnimation; private View.OnClickListener videoOnClickListener; @@ -276,7 +288,7 @@ public class CallController extends BaseController { @Parcel public enum CallStatus { - CALLING, CALLING_TIMEOUT, ESTABLISHED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING, PUBLISHER_FAILED + CONNECTING, CALLING_TIMEOUT, JOINED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING, PUBLISHER_FAILED } public CallController(Bundle args) { @@ -287,8 +299,13 @@ public class CallController extends BaseController { roomToken = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), ""); conversationUser = args.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()); conversationPassword = args.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), ""); + conversationName = args.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), ""); isVoiceOnlyCall = args.getBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false); + if (args.containsKey(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL())) { + isIncomingCallFromNotification = args.getBoolean(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL()); + } + credentials = ApiUtils.getCredentials(conversationUser.getUsername(), conversationUser.getToken()); baseUrl = args.getString(BundleKeys.INSTANCE.getKEY_MODIFIED_BASE_URL(), ""); @@ -298,11 +315,11 @@ public class CallController extends BaseController { } powerManagerUtils = new PowerManagerUtils(); - + if (args.getString("state", "").equalsIgnoreCase("resume")) { setCallState(CallStatus.IN_CONVERSATION); } else { - setCallState(CallStatus.CALLING); + setCallState(CallStatus.CONNECTING); } } @@ -362,7 +379,7 @@ public class CallController extends BaseController { //Create a new PeerConnectionFactory instance. PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory( - rootEglBase.getEglBaseContext(),true,true); + rootEglBase.getEglBaseContext(), true, true); DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext()); peerConnectionFactory = PeerConnectionFactory.builder() @@ -456,7 +473,12 @@ public class CallController extends BaseController { cameraSwitchButton.setVisibility(View.GONE); cameraControlButton.setVisibility(View.GONE); pipVideoView.setVisibility(View.GONE); + + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.addRule(RelativeLayout.BELOW, R.id.callInfosLinearLayout); + remoteRenderersLayout.setLayoutParams(params); } else { + callControlEnableSpeaker.setVisibility(View.GONE); if (cameraEnumerator.getDeviceNames().length < 2) { cameraSwitchButton.setVisibility(View.GONE); } @@ -484,7 +506,7 @@ public class CallController extends BaseController { } private boolean isConnectionEstablished() { - return (currentCallStatus.equals(CallStatus.ESTABLISHED) || currentCallStatus.equals(CallStatus.IN_CONVERSATION)); + return (currentCallStatus.equals(CallStatus.JOINED) || currentCallStatus.equals(CallStatus.IN_CONVERSATION)); } @AfterPermissionGranted(100) @@ -648,6 +670,7 @@ public class CallController extends BaseController { boolean onMicrophoneLongClick() { if (!audioOn) { callControlHandler.removeCallbacksAndMessages(null); + callInfosHandler.removeCallbacksAndMessages(null); cameraSwitchHandler.removeCallbacksAndMessages(null); isPTTActive = true; callControls.setVisibility(View.VISIBLE); @@ -735,7 +758,7 @@ public class CallController extends BaseController { } } } - + @OnClick(R.id.callControlToggleChat) void onToggleChatClick() { ((MagicCallActivity) getActivity()).showChat(); @@ -868,6 +891,7 @@ public class CallController extends BaseController { if (show) { callControlHandler.removeCallbacksAndMessages(null); + callInfosHandler.removeCallbacksAndMessages(null); cameraSwitchHandler.removeCallbacksAndMessages(null); alpha = 1.0f; duration = 1000; @@ -875,8 +899,13 @@ public class CallController extends BaseController { callControls.setAlpha(0.0f); callControls.setVisibility(View.VISIBLE); + callInfosLinearLayout.setAlpha(0.0f); + callInfosLinearLayout.setVisibility(View.VISIBLE); + cameraSwitchButton.setAlpha(0.0f); - cameraSwitchButton.setVisibility(View.VISIBLE); + if (videoOn) { + cameraSwitchButton.setVisibility(View.VISIBLE); + } } else { callControlHandler.postDelayed(() -> animateCallControls(false, 0), 5000); return; @@ -920,6 +949,37 @@ public class CallController extends BaseController { }); } + if (callInfosLinearLayout != null) { + callInfosLinearLayout.setEnabled(false); + callInfosLinearLayout.animate() + .translationY(0) + .alpha(alpha) + .setDuration(duration) + .setStartDelay(startDelay) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (callInfosLinearLayout != null) { + if (!show) { + callInfosLinearLayout.setVisibility(View.GONE); + } else { + callInfosHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (!isPTTActive) { + animateCallControls(false, 0); + } + } + }, 7500); + } + + callInfosLinearLayout.setEnabled(true); + } + } + }); + } + if (cameraSwitchButton != null) { cameraSwitchButton.setEnabled(false); cameraSwitchButton.animate() @@ -1139,8 +1199,15 @@ public class CallController extends BaseController { } private void performCall() { + Integer inCallFlag; + if (isVoiceOnlyCall) { + inCallFlag = (int) Participant.ParticipantFlags.IN_CALL_WITH_AUDIO.getValue(); + } else { + inCallFlag = (int) Participant.ParticipantFlags.IN_CALL_WITH_AUDIO_AND_VIDEO.getValue(); + } + ncApi.joinCall(credentials, - ApiUtils.getUrlForCall(baseUrl, roomToken)) + ApiUtils.getUrlForCall(baseUrl, roomToken), inCallFlag) .subscribeOn(Schedulers.io()) .retry(3) .observeOn(AndroidSchedulers.mainThread()) @@ -1153,7 +1220,7 @@ public class CallController extends BaseController { @Override public void onNext(GenericOverall genericOverall) { if (!currentCallStatus.equals(CallStatus.LEAVING)) { - setCallState(CallStatus.ESTABLISHED); + setCallState(CallStatus.JOINED); ApplicationWideCurrentRoomHolder.getInstance().setInCall(true); @@ -1339,7 +1406,7 @@ public class CallController extends BaseController { private void receivedSignalingMessage(Signaling signaling) throws IOException { String messageType = signaling.getType(); - if (!isConnectionEstablished() && !currentCallStatus.equals(CallStatus.CALLING)) { + if (!isConnectionEstablished() && !currentCallStatus.equals(CallStatus.CONNECTING)) { return; } @@ -1464,38 +1531,38 @@ public class CallController extends BaseController { } private void hangupNetworkCalls(boolean shutDownView) { - ncApi.leaveCall(credentials, ApiUtils.getUrlForCall(baseUrl, roomToken)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { + ncApi.leaveCall(credentials, ApiUtils.getUrlForCall(baseUrl, roomToken)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { - } + } - @Override - public void onNext(GenericOverall genericOverall) { - if (isMultiSession) { - if (shutDownView && getActivity() != null) { - getActivity().finish(); - } else if (!shutDownView && (currentCallStatus.equals(CallStatus.RECONNECTING) || currentCallStatus.equals(CallStatus.PUBLISHER_FAILED))) { - initiateCall(); - } - } else { - leaveRoom(shutDownView); + @Override + public void onNext(GenericOverall genericOverall) { + if (isMultiSession) { + if (shutDownView && getActivity() != null) { + getActivity().finish(); + } else if (!shutDownView && (currentCallStatus.equals(CallStatus.RECONNECTING) || currentCallStatus.equals(CallStatus.PUBLISHER_FAILED))) { + initiateCall(); } + } else { + leaveRoom(shutDownView); } + } - @Override - public void onError(Throwable e) { + @Override + public void onError(Throwable e) { - } + } - @Override - public void onComplete() { + @Override + public void onComplete() { - } - }); + } + }); } private void leaveRoom(boolean shutDownView) { @@ -1567,7 +1634,7 @@ public class CallController extends BaseController { // Calculate sessions that join the call newSessions.removeAll(oldSesssions); - if (!isConnectionEstablished() && !currentCallStatus.equals(CallStatus.CALLING)) { + if (!isConnectionEstablished() && !currentCallStatus.equals(CallStatus.CONNECTING)) { return; } @@ -1759,7 +1826,7 @@ public class CallController extends BaseController { boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent .PeerConnectionEventType.SENSOR_FAR) && videoOn; if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) && - (currentCallStatus.equals(CallStatus.CALLING) || isConnectionEstablished()) && videoOn + (currentCallStatus.equals(CallStatus.CONNECTING) || isConnectionEstablished()) && videoOn && enableVideo != localVideoTrack.enabled()) { toggleMedia(enableVideo, true); } @@ -2066,7 +2133,7 @@ public class CallController extends BaseController { private void gotNick(String sessionId, String nick, String type) { String remoteRendererTag = sessionId + "+" + type; - if (relativeLayout != null) { + if (controllerCallLayout != null) { RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(remoteRendererTag); TextView textView = relativeLayout.findViewById(R.id.peer_nick_text_view); if (!textView.getText().equals(nick)) { @@ -2079,7 +2146,7 @@ public class CallController extends BaseController { } } - @OnClick(R.id.connectingRelativeLayoutView) + @OnClick(R.id.callStateRelativeLayoutView) public void onConnectingViewClick() { if (currentCallStatus.equals(CallStatus.CALLING_TIMEOUT)) { setCallState(CallStatus.RECONNECTING); @@ -2097,16 +2164,24 @@ public class CallController extends BaseController { } switch (callState) { - case CALLING: + case CONNECTING: handler.post(() -> { playCallingSound(); - connectingTextView.setText(R.string.nc_connecting_call); - if (connectingView.getVisibility() != View.VISIBLE) { - connectingView.setVisibility(View.VISIBLE); + if (isIncomingCallFromNotification) { + callStateTextView.setText(R.string.nc_call_incoming); + } else { + callStateTextView.setText(R.string.nc_call_ringing); + } + callConversationNameTextView.setText(conversationName); + + callVoiceOrVideoTextView.setText(isVoiceOnlyCall ? R.string.nc_voice_call : R.string.nc_video_call); + + if (callStateView.getVisibility() != View.VISIBLE) { + callStateView.setVisibility(View.VISIBLE); } - if (conversationView.getVisibility() != View.INVISIBLE) { - conversationView.setVisibility(View.INVISIBLE); + if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) { + remoteRenderersLayout.setVisibility(View.INVISIBLE); } if (progressBar.getVisibility() != View.VISIBLE) { @@ -2121,17 +2196,18 @@ public class CallController extends BaseController { case CALLING_TIMEOUT: handler.post(() -> { hangup(false); - connectingTextView.setText(R.string.nc_call_timeout); - if (connectingView.getVisibility() != View.VISIBLE) { - connectingView.setVisibility(View.VISIBLE); + callStateTextView.setText(R.string.nc_call_timeout); + callVoiceOrVideoTextView.setText(isVoiceOnlyCall ? R.string.nc_voice_call : R.string.nc_video_call); + if (callStateView.getVisibility() != View.VISIBLE) { + callStateView.setVisibility(View.VISIBLE); } if (progressBar.getVisibility() != View.GONE) { progressBar.setVisibility(View.GONE); } - if (conversationView.getVisibility() != View.INVISIBLE) { - conversationView.setVisibility(View.INVISIBLE); + if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) { + remoteRenderersLayout.setVisibility(View.INVISIBLE); } errorImageView.setImageResource(R.drawable.ic_av_timer_timer_24dp); @@ -2144,12 +2220,13 @@ public class CallController extends BaseController { case RECONNECTING: handler.post(() -> { playCallingSound(); - connectingTextView.setText(R.string.nc_call_reconnecting); - if (connectingView.getVisibility() != View.VISIBLE) { - connectingView.setVisibility(View.VISIBLE); + callStateTextView.setText(R.string.nc_call_reconnecting); + callVoiceOrVideoTextView.setText(isVoiceOnlyCall ? R.string.nc_voice_call : R.string.nc_video_call); + if (callStateView.getVisibility() != View.VISIBLE) { + callStateView.setVisibility(View.VISIBLE); } - if (conversationView.getVisibility() != View.INVISIBLE) { - conversationView.setVisibility(View.INVISIBLE); + if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) { + remoteRenderersLayout.setVisibility(View.INVISIBLE); } if (progressBar.getVisibility() != View.VISIBLE) { progressBar.setVisibility(View.VISIBLE); @@ -2160,13 +2237,18 @@ public class CallController extends BaseController { } }); break; - case ESTABLISHED: + case JOINED: handler.postDelayed(() -> setCallState(CallStatus.CALLING_TIMEOUT), 45000); handler.post(() -> { - if (connectingView != null) { - connectingTextView.setText(R.string.nc_calling); - if (connectingTextView.getVisibility() != View.VISIBLE) { - connectingView.setVisibility(View.VISIBLE); + callVoiceOrVideoTextView.setText(isVoiceOnlyCall ? R.string.nc_voice_call : R.string.nc_video_call); + if (callStateView != null) { + if (isIncomingCallFromNotification) { + callStateTextView.setText(R.string.nc_call_incoming); + } else { + callStateTextView.setText(R.string.nc_call_ringing); + } + if (callStateView.getVisibility() != View.VISIBLE) { + callStateView.setVisibility(View.VISIBLE); } } @@ -2176,9 +2258,9 @@ public class CallController extends BaseController { } } - if (conversationView != null) { - if (conversationView.getVisibility() != View.INVISIBLE) { - conversationView.setVisibility(View.INVISIBLE); + if (remoteRenderersLayout != null) { + if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) { + remoteRenderersLayout.setVisibility(View.INVISIBLE); } } @@ -2192,14 +2274,19 @@ public class CallController extends BaseController { case IN_CONVERSATION: handler.post(() -> { stopCallingSound(); + callVoiceOrVideoTextView.setText(isVoiceOnlyCall ? R.string.nc_voice_call : R.string.nc_video_call); + + if (!isVoiceOnlyCall) { + callInfosLinearLayout.setVisibility(View.GONE); + } if (!isPTTActive) { animateCallControls(false, 5000); } - if (connectingView != null) { - if (connectingView.getVisibility() != View.INVISIBLE) { - connectingView.setVisibility(View.INVISIBLE); + if (callStateView != null) { + if (callStateView.getVisibility() != View.INVISIBLE) { + callStateView.setVisibility(View.INVISIBLE); } } @@ -2209,9 +2296,9 @@ public class CallController extends BaseController { } } - if (conversationView != null) { - if (conversationView.getVisibility() != View.VISIBLE) { - conversationView.setVisibility(View.VISIBLE); + if (remoteRenderersLayout != null) { + if (remoteRenderersLayout.getVisibility() != View.VISIBLE) { + remoteRenderersLayout.setVisibility(View.VISIBLE); } } @@ -2226,18 +2313,18 @@ public class CallController extends BaseController { handler.post(() -> { stopCallingSound(); - if (connectingTextView != null) { - connectingTextView.setText(R.string.nc_offline); + if (callStateTextView != null) { + callStateTextView.setText(R.string.nc_offline); - if (connectingView.getVisibility() != View.VISIBLE) { - connectingView.setVisibility(View.VISIBLE); + if (callStateView.getVisibility() != View.VISIBLE) { + callStateView.setVisibility(View.VISIBLE); } } - if (conversationView != null) { - if (conversationView.getVisibility() != View.INVISIBLE) { - conversationView.setVisibility(View.INVISIBLE); + if (remoteRenderersLayout != null) { + if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) { + remoteRenderersLayout.setVisibility(View.INVISIBLE); } } @@ -2259,9 +2346,10 @@ public class CallController extends BaseController { handler.post(() -> { if (!isDestroyed() && !isBeingDestroyed()) { stopCallingSound(); - connectingTextView.setText(R.string.nc_leaving_call); - connectingView.setVisibility(View.VISIBLE); - conversationView.setVisibility(View.INVISIBLE); + callVoiceOrVideoTextView.setText(isVoiceOnlyCall ? R.string.nc_voice_call : R.string.nc_video_call); + callStateTextView.setText(R.string.nc_leaving_call); + callStateView.setVisibility(View.VISIBLE); + remoteRenderersLayout.setVisibility(View.INVISIBLE); progressBar.setVisibility(View.VISIBLE); errorImageView.setVisibility(View.GONE); } @@ -2274,7 +2362,14 @@ public class CallController extends BaseController { private void playCallingSound() { stopCallingSound(); - Uri ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/raw/librem_by_feandesign_call"); + Uri ringtoneUri; + if (isIncomingCallFromNotification) { + ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + + "/raw/librem_by_feandesign_call"); + } else { + ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/raw" + + "/tr110_1_kap8_3_freiton1"); + } if (getActivity() != null) { mediaPlayer = new MediaPlayer(); try { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java index 51c7339da..879c0ce51 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java @@ -70,6 +70,7 @@ import com.nextcloud.talk.events.ConfigurationChangeEvent; import com.nextcloud.talk.models.RingtoneSettings; import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.json.conversations.Conversation; +import com.nextcloud.talk.models.json.conversations.RoomOverall; import com.nextcloud.talk.models.json.conversations.RoomsOverall; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.participants.ParticipantsOverall; @@ -83,6 +84,7 @@ import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import org.jetbrains.annotations.NotNull; import org.michaelevans.colorart.library.ColorArt; import org.parceler.Parcels; @@ -121,6 +123,9 @@ public class CallNotificationController extends BaseController { @Inject Context context; + @BindView(R.id.incomingCallVoiceOrVideoTextView) + TextView incomingCallVoiceOrVideoTextView; + @BindView(R.id.conversationNameTextView) TextView conversationNameTextView; @@ -197,6 +202,8 @@ public class CallNotificationController extends BaseController { private void proceedToCall() { originalBundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), currentConversation.getToken()); + originalBundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), currentConversation.getDisplayName()); + originalBundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), currentConversation.getDisplayName()); getRouter().replaceTopController(RouterTransaction.with(new CallController(originalBundle)) .popChangeHandler(new HorizontalChangeHandler()) @@ -253,38 +260,77 @@ public class CallNotificationController extends BaseController { } private void handleFromNotification() { - ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(userBeingCalled.getBaseUrl())) - .subscribeOn(Schedulers.io()) - .retry(3) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - disposablesList.add(d); - } + boolean isConversationApiV3 = userBeingCalled.hasSpreedFeatureCapability("conversation-v3"); + if(isConversationApiV3) { + ncApi.getRoom(credentials, ApiUtils.getRoomV3(userBeingCalled.getBaseUrl(), roomId)) + .subscribeOn(Schedulers.io()) + .retry(3) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + disposablesList.add(d); + } - @Override - public void onNext(RoomsOverall roomsOverall) { - for (Conversation conversation : roomsOverall.getOcs().getData()) { - if (roomId.equals(conversation.getRoomId()) || roomId.equals(conversation.token)) { - currentConversation = conversation; - runAllThings(); - break; + @Override + public void onNext(@NotNull RoomOverall roomOverall) { + currentConversation = roomOverall.getOcs().data; + runAllThings(); + + boolean hasCallFlags = userBeingCalled.hasSpreedFeatureCapability("conversation-call-flags"); + if (hasCallFlags) { + if (isInCallWithVideo(currentConversation.callFlag)){ + incomingCallVoiceOrVideoTextView.setText(R.string.nc_video_call); + } else { + incomingCallVoiceOrVideoTextView.setText(R.string.nc_voice_call); + } } } - } + @Override + public void onError(Throwable e) { - @Override - public void onError(Throwable e) { + } - } + @Override + public void onComplete() { - @Override - public void onComplete() { + } + }); + } else { + ncApi.getRoom(credentials, ApiUtils.getRoom(userBeingCalled.getBaseUrl(), roomId)) + .subscribeOn(Schedulers.io()) + .retry(3) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + disposablesList.add(d); + } - } - }); + @SuppressLint("LongLogTag") + @Override + public void onNext(@NotNull RoomOverall roomOverall) { + currentConversation = roomOverall.getOcs().data; + runAllThings(); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + } + } + + private boolean isInCallWithVideo(int callFlag) { + return (Participant.ParticipantFlags.IN_CALL_WITH_VIDEO.getValue() == callFlag + || Participant.ParticipantFlags.IN_CALL_WITH_AUDIO_AND_VIDEO.getValue() == callFlag); } private void runAllThings() { @@ -321,73 +367,14 @@ public class CallNotificationController extends BaseController { } if (DoNotDisturbUtils.INSTANCE.shouldPlaySound()) { - String callRingtonePreferenceString = appPreferences.getCallRingtoneUri(); - Uri ringtoneUri; - - if (TextUtils.isEmpty(callRingtonePreferenceString)) { - // play default sound - ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + - "/raw/librem_by_feandesign_call"); - } else { - try { - RingtoneSettings ringtoneSettings = LoganSquare.parse(callRingtonePreferenceString, RingtoneSettings.class); - ringtoneUri = ringtoneSettings.getRingtoneUri(); - } catch (IOException e) { - Log.e(TAG, "Failed to parse ringtone settings"); - ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + - "/raw/librem_by_feandesign_call"); - } - } - - if (ringtoneUri != null && getActivity() != null) { - mediaPlayer = new MediaPlayer(); - try { - mediaPlayer.setDataSource(getActivity(), ringtoneUri); - - mediaPlayer.setLooping(true); - AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes - .CONTENT_TYPE_SONIFICATION).setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); - mediaPlayer.setAudioAttributes(audioAttributes); - - mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start()); - - mediaPlayer.prepareAsync(); - } catch (IOException e) { - Log.e(TAG, "Failed to set data source"); - } - } + playRingtoneSound(); } if (DoNotDisturbUtils.INSTANCE.shouldVibrate(appPreferences.getShouldVibrateSetting())) { - vibrator = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); - - if (vibrator != null) { - long[] vibratePattern = new long[]{0, 400, 800, 600, 800, 800, 800, 1000}; - int[] amplitudes = new int[]{0, 255, 0, 255, 0, 255, 0, 255}; - - VibrationEffect vibrationEffect; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (vibrator.hasAmplitudeControl()) { - vibrationEffect = VibrationEffect.createWaveform(vibratePattern, amplitudes, -1); - //vibrator.vibrate(vibrationEffect); - } else { - vibrationEffect = VibrationEffect.createWaveform(vibratePattern, -1); - //vibrator.vibrate(vibrationEffect); - } - } else { - //vibrator.vibrate(vibratePattern, -1); - } - } - - handler.postDelayed(() -> { - if (vibrator != null) { - vibrator.cancel(); - } - }, 10000); + vibrate(); } } - @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(ConfigurationChangeEvent configurationChangeEvent) { ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) avatarImageView.getLayoutParams(); @@ -509,4 +496,71 @@ public class CallNotificationController extends BaseController { } } } + + @SuppressLint("LongLogTag") + private void playRingtoneSound() { + String callRingtonePreferenceString = appPreferences.getCallRingtoneUri(); + Uri ringtoneUri; + + if (TextUtils.isEmpty(callRingtonePreferenceString)) { + // play default sound + ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + + "/raw/librem_by_feandesign_call"); + } else { + try { + RingtoneSettings ringtoneSettings = LoganSquare.parse(callRingtonePreferenceString, RingtoneSettings.class); + ringtoneUri = ringtoneSettings.getRingtoneUri(); + } catch (IOException e) { + Log.e(TAG, "Failed to parse ringtone settings"); + ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + + "/raw/librem_by_feandesign_call"); + } + } + + if (ringtoneUri != null && getActivity() != null) { + mediaPlayer = new MediaPlayer(); + try { + mediaPlayer.setDataSource(getActivity(), ringtoneUri); + + mediaPlayer.setLooping(true); + AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes + .CONTENT_TYPE_SONIFICATION).setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); + mediaPlayer.setAudioAttributes(audioAttributes); + + mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start()); + + mediaPlayer.prepareAsync(); + } catch (IOException e) { + Log.e(TAG, "Failed to set data source"); + } + } + } + + private void vibrate() { + vibrator = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); + + if (vibrator != null) { + long[] vibratePattern = new long[]{0, 400, 800, 600, 800, 800, 800, 1000}; + int[] amplitudes = new int[]{0, 255, 0, 255, 0, 255, 0, 255}; + + VibrationEffect vibrationEffect; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (vibrator.hasAmplitudeControl()) { + vibrationEffect = VibrationEffect.createWaveform(vibratePattern, amplitudes, -1); + //vibrator.vibrate(vibrationEffect); + } else { + vibrationEffect = VibrationEffect.createWaveform(vibratePattern, -1); + //vibrator.vibrate(vibrationEffect); + } + } else { + //vibrator.vibrate(vibratePattern, -1); + } + } + + handler.postDelayed(() -> { + if (vibrator != null) { + vibrator.cancel(); + } + }, 10000); + } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 46b5aa4da..f960d8688 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -1395,41 +1395,34 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter private fun startACall(isVoiceOnlyCall: Boolean) { isLeavingForConversation = true - if (!isVoiceOnlyCall) { - val videoCallIntent = getIntentForCall(false) - if (videoCallIntent != null) { - startActivity(videoCallIntent) - } - } else { - val voiceCallIntent = getIntentForCall(true) - if (voiceCallIntent != null) { - startActivity(voiceCallIntent) - } + val callIntent = getIntentForCall(isVoiceOnlyCall) + if (callIntent != null) { + startActivity(callIntent) } } private fun getIntentForCall(isVoiceOnlyCall: Boolean): Intent? { - if (currentConversation != null) { + currentConversation?.let { val bundle = Bundle() bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken) bundle.putString(BundleKeys.KEY_ROOM_ID, roomId) bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser) bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword) bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl) + bundle.putString(BundleKeys.KEY_CONVERSATION_NAME, it.displayName) if (isVoiceOnlyCall) { bundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true) } - if (activity != null) { + return if (activity != null) { val callIntent = Intent(activity, MagicCallActivity::class.java) callIntent.putExtras(bundle) - - return callIntent + callIntent } else { - return null + null } - } else { + } ?:run { return null } } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java index ed990f941..0f1ebc214 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java @@ -94,6 +94,8 @@ public class Conversation { public Long lobbyTimer; @JsonField(name = "lastReadMessage") public int lastReadMessage; + @JsonField(name = "callFlag") + public int callFlag; public boolean isPublic() { return (ConversationType.ROOM_PUBLIC_CALL.equals(type)); diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 76159e641..82b80f0c3 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -127,6 +127,10 @@ public class ApiUtils { return baseUrl + ocsApiVersion + spreedApiVersion + "/room/" + token; } + public static String getRoomV3(String baseUrl, String token) { + return baseUrl + ocsApiVersion + "/apps/spreed/api/v3" + "/room/" + token; + } + public static RetrofitBucket getRetrofitBucketForCreateRoom(String baseUrl, String roomType, @Nullable String invite, @Nullable String conversationName) { diff --git a/app/src/main/res/drawable/ic_comment_white.xml b/app/src/main/res/drawable/ic_comment_white.xml new file mode 100644 index 000000000..74f759c9e --- /dev/null +++ b/app/src/main/res/drawable/ic_comment_white.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/app/src/main/res/layout/call_item.xml b/app/src/main/res/layout/call_item.xml index 2ab2db2d2..5e9e49bab 100644 --- a/app/src/main/res/layout/call_item.xml +++ b/app/src/main/res/layout/call_item.xml @@ -26,6 +26,18 @@ android:layout_weight="1" android:orientation="vertical"> + + - - + android:layout_height="wrap_content"> + ~ @author Marcel Hibbe + ~ Copyright (C) 2021 Marcel Hibbe ~ ~ 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 @@ -19,135 +21,176 @@ --> - - - + android:orientation="vertical" + tools:context=".activities.MagicCallActivity"> + - + android:visibility="visible" + android:layout_weight="1" + tools:visibility="visible"> - - + + - + + + + + + + + + + + + + + + + + + + + + + android:animateLayoutChanges="true" + android:orientation="horizontal" + android:background="@android:color/transparent" + android:gravity="center" + android:layout_alignBottom="@id/linearWrapperLayout" + android:layout_marginBottom="10dp"> - + + - + - + - - - - - - - - + - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/controller_call_notification.xml b/app/src/main/res/layout/controller_call_notification.xml index ee7c6dc8f..4ed9351c9 100644 --- a/app/src/main/res/layout/controller_call_notification.xml +++ b/app/src/main/res/layout/controller_call_notification.xml @@ -47,7 +47,8 @@ android:visibility="gone" app:backgroundImage="@color/nc_darkGreen" app:placeholderImage="@drawable/ic_call_white_24dp" - app:roundAsCircle="true" /> + app:roundAsCircle="true" + tools:visibility="visible"/> + app:roundAsCircle="true" + tools:visibility="visible"/> + + + android:visibility="gone" + tools:visibility="visible"/> + android:visibility="gone" + tools:visibility="visible"/> #800082C9 #46ffffff + + #BF999999 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 912c87291..23eb125a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -184,6 +184,11 @@ Open settings + Nextcloud Talk voice call + Nextcloud Talk video call + Nextcloud Talk call + INCOMING + RINGING Connecting… Calling… Incoming call from