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 89cafc7ef..54824fcd1 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallController.java @@ -26,9 +26,13 @@ import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; import android.content.res.Configuration; import android.graphics.Color; +import android.media.AudioAttributes; +import android.media.MediaPlayer; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -143,6 +147,12 @@ public class CallController extends BaseController { @BindView(R.id.conversationRelativeLayoutView) RelativeLayout conversationView; + @BindView(R.id.errorImageView) + ImageView errorImageView; + + @BindView(R.id.progress_bar) + ProgressBar progressBar; + @Inject NcApi ncApi; @Inject @@ -210,9 +220,11 @@ public class CallController extends BaseController { private CallStatus currentCallStatus; + private MediaPlayer mediaPlayer; + @Parcel public enum CallStatus { - CALLING, CALLING_TIMEOUT, ESTABLISHED, RECONNECTING, OFFLINE, LEAVING + CALLING, CALLING_TIMEOUT, ESTABLISHED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING } public CallController(Bundle args) { @@ -234,7 +246,7 @@ public class CallController extends BaseController { } powerManagerUtils = new PowerManagerUtils(); - currentCallStatus = CallStatus.CALLING; + setCallState(CallStatus.CALLING); } @Override @@ -409,6 +421,10 @@ public class CallController extends BaseController { } + private boolean isConnectionEstablished() { + return (currentCallStatus.equals(CallStatus.ESTABLISHED) || currentCallStatus.equals(CallStatus.IN_CONVERSATION)); + } + @AfterPermissionGranted(100) private void onPermissionsGranted() { if (EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CALL)) { @@ -430,7 +446,7 @@ public class CallController extends BaseController { } } - if (!currentCallStatus.equals(CallStatus.ESTABLISHED)) { + if (!isConnectionEstablished()) { fetchSignalingSettings(); } } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(), @@ -469,7 +485,7 @@ public class CallController extends BaseController { microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); } - if (!currentCallStatus.equals(CallStatus.ESTABLISHED)) { + if (!isConnectionEstablished()) { fetchSignalingSettings(); } } @@ -487,7 +503,7 @@ public class CallController extends BaseController { if (getActivity() != null && (EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) || EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_MICROPHONE))) { checkIfSomeAreApproved(); - } else if (!currentCallStatus.equals(CallStatus.ESTABLISHED)) { + } else if (!isConnectionEstablished()) { fetchSignalingSettings(); } } @@ -637,7 +653,7 @@ public class CallController extends BaseController { toggleMedia(true, false); } - if (isVoiceOnlyCall && !currentCallStatus.equals(CallStatus.ESTABLISHED)) { + if (isVoiceOnlyCall && !isConnectionEstablished()) { fetchSignalingSettings(); } @@ -659,7 +675,7 @@ public class CallController extends BaseController { @OnClick(R.id.callControlHangupView) void onHangupClick() { - currentCallStatus = CallStatus.LEAVING; + setCallState(CallStatus.LEAVING); hangup(true); } @@ -755,7 +771,7 @@ public class CallController extends BaseController { } } - if (currentCallStatus.equals(CallStatus.ESTABLISHED)) { + if (isConnectionEstablished()) { if (!hasMCU) { for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) { magicPeerConnectionWrapperList.get(i).sendChannelData(new DataChannelMessage(message)); @@ -1058,19 +1074,7 @@ public class CallController extends BaseController { @Override public void onNext(GenericOverall genericOverall) { - currentCallStatus = CallStatus.ESTABLISHED; - - if (connectingView != null) { - connectingView.setVisibility(View.GONE); - } - - if (conversationView != null) { - conversationView.setVisibility(View.VISIBLE); - } - - if (!isPTTActive) { - animateCallControls(false, 5000); - } + setCallState(CallStatus.ESTABLISHED); ApplicationWideCurrentRoomHolder.getInstance().setInCall(true); @@ -1079,8 +1083,8 @@ public class CallController extends BaseController { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .repeatWhen(observable -> observable.delay(5000, TimeUnit.MILLISECONDS)) - .takeWhile(observable -> currentCallStatus.equals(CallStatus.ESTABLISHED)) - .retry(3, observable -> currentCallStatus.equals(CallStatus.ESTABLISHED)) + .takeWhile(observable -> isConnectionEstablished()) + .retry(3, observable -> isConnectionEstablished()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { @@ -1121,8 +1125,8 @@ public class CallController extends BaseController { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .repeatWhen(observable -> observable) - .takeWhile(observable -> currentCallStatus.equals(CallStatus.ESTABLISHED)) - .retry(3, observable -> currentCallStatus.equals(CallStatus.ESTABLISHED)) + .takeWhile(observable -> isConnectionEstablished()) + .retry(3, observable -> isConnectionEstablished()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { @@ -1246,7 +1250,7 @@ public class CallController extends BaseController { private void receivedSignalingMessage(Signaling signaling) throws IOException { String messageType = signaling.getType(); - if (!currentCallStatus.equals(CallStatus.ESTABLISHED)) { + if (!isConnectionEstablished()) { return; } @@ -1316,6 +1320,7 @@ public class CallController extends BaseController { } private void hangup(boolean shutDownView) { + stopCallingSound(); dispose(null); if (shutDownView) { @@ -1480,7 +1485,7 @@ public class CallController extends BaseController { // Calculate sessions that join the call newSessions.removeAll(oldSesssions); - if (!currentCallStatus.equals(CallStatus.ESTABLISHED)) { + if (!isConnectionEstablished()) { return; } @@ -1494,6 +1499,10 @@ public class CallController extends BaseController { getPeerConnectionWrapperForSessionIdAndType(sessionId, "video", hasMCU && sessionId.equals(webSocketClient.getSessionId())); } + if (newSessions.size() > 0 && !currentCallStatus.equals(CallStatus.IN_CONVERSATION)) { + setCallState(CallStatus.IN_CONVERSATION); + } + for (String sessionId : oldSesssions) { endPeerConnection(sessionId, false); } @@ -1662,7 +1671,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) || currentCallStatus.equals(CallStatus.ESTABLISHED)) && videoOn + (currentCallStatus.equals(CallStatus.CALLING) || isConnectionEstablished()) && videoOn && enableVideo != localVideoTrack.enabled()) { toggleMedia(enableVideo, true); } @@ -1693,7 +1702,7 @@ public class CallController extends BaseController { int finalI = i; Observable .interval(1, TimeUnit.SECONDS) - .takeWhile(observer -> currentCallStatus.equals(CallStatus.ESTABLISHED)) + .takeWhile(observer -> isConnectionEstablished()) .observeOn(Schedulers.io()) .doOnNext(n -> magicPeerConnectionWrapperList.get(finalI).sendChannelData(dataChannelMessage)); break; @@ -1946,6 +1955,206 @@ public class CallController extends BaseController { } } + @OnClick(R.id.connectingRelativeLayoutView) + public void onConnectingViewClick() { + if (currentCallStatus.equals(CallStatus.CALLING_TIMEOUT)) { + setCallState(CallStatus.RECONNECTING); + hangupNetworkCalls(false); + } + } + + private void setCallState(CallStatus callState) { + if (currentCallStatus == null || !currentCallStatus.equals(callState)) { + currentCallStatus = callState; + if (handler == null) { + handler = new Handler(Looper.getMainLooper()); + } else { + handler.removeCallbacksAndMessages(null); + } + + switch (callState) { + case CALLING: + handler.post(() -> { + playCallingSound(); + connectingTextView.setText(R.string.nc_connecting_call); + if (connectingView.getVisibility() != View.VISIBLE) { + connectingView.setVisibility(View.VISIBLE); + } + + if (conversationView.getVisibility() != View.INVISIBLE) { + conversationView.setVisibility(View.INVISIBLE); + } + + if (progressBar.getVisibility() != View.VISIBLE) { + progressBar.setVisibility(View.VISIBLE); + } + + if (errorImageView.getVisibility() != View.GONE) { + errorImageView.setVisibility(View.GONE); + } + }); + break; + case CALLING_TIMEOUT: + handler.post(() -> { + hangup(false); + connectingTextView.setText(R.string.nc_call_timeout); + if (connectingView.getVisibility() != View.VISIBLE) { + connectingView.setVisibility(View.VISIBLE); + } + + if (progressBar.getVisibility() != View.GONE) { + progressBar.setVisibility(View.GONE); + } + + if (conversationView.getVisibility() != View.INVISIBLE) { + conversationView.setVisibility(View.INVISIBLE); + } + + errorImageView.setImageResource(R.drawable.ic_av_timer_timer_24dp); + + if (errorImageView.getVisibility() != View.VISIBLE) { + errorImageView.setVisibility(View.VISIBLE); + } + }); + break; + case RECONNECTING: + handler.post(() -> { + playCallingSound(); + connectingTextView.setText(R.string.nc_call_reconnecting); + if (connectingView.getVisibility() != View.VISIBLE) { + connectingView.setVisibility(View.VISIBLE); + } + if (conversationView.getVisibility() != View.INVISIBLE) { + conversationView.setVisibility(View.INVISIBLE); + } + if (progressBar.getVisibility() != View.VISIBLE) { + progressBar.setVisibility(View.VISIBLE); + } + + if (errorImageView.getVisibility() != View.GONE) { + errorImageView.setVisibility(View.GONE); + } + }); + break; + case ESTABLISHED: + handler.postDelayed(() -> setCallState(CallStatus.CALLING_TIMEOUT), 45000); + handler.post(() -> { + connectingTextView.setText(R.string.nc_calling); + if (connectingTextView.getVisibility() != View.VISIBLE) { + connectingView.setVisibility(View.VISIBLE); + } + + if (progressBar.getVisibility() != View.VISIBLE) { + progressBar.setVisibility(View.VISIBLE); + } + + if (conversationView.getVisibility() != View.INVISIBLE) { + conversationView.setVisibility(View.INVISIBLE); + } + + if (errorImageView.getVisibility() != View.GONE) { + errorImageView.setVisibility(View.GONE); + } + }); + break; + case IN_CONVERSATION: + handler.post(() -> { + stopCallingSound(); + + if (!isPTTActive) { + animateCallControls(false, 5000); + } + + if (connectingView.getVisibility() != View.INVISIBLE) { + connectingView.setVisibility(View.INVISIBLE); + } + + if (progressBar.getVisibility() != View.GONE) { + progressBar.setVisibility(View.GONE); + } + + if (conversationView.getVisibility() != View.VISIBLE) { + conversationView.setVisibility(View.VISIBLE); + } + + if (errorImageView.getVisibility() != View.GONE) { + errorImageView.setVisibility(View.GONE); + } + }); + break; + case OFFLINE: + handler.post(() -> { + stopCallingSound(); + connectingTextView.setText(R.string.nc_offline); + + if (connectingView.getVisibility() != View.VISIBLE) { + connectingView.setVisibility(View.VISIBLE); + } + + if (conversationView.getVisibility() != View.INVISIBLE) { + conversationView.setVisibility(View.INVISIBLE); + } + + if (progressBar.getVisibility() != View.GONE) { + progressBar.setVisibility(View.GONE); + } + + errorImageView.setImageResource(R.drawable.ic_signal_wifi_off_white_24dp); + if (errorImageView.getVisibility() != View.VISIBLE) { + errorImageView.setVisibility(View.VISIBLE); + } + }); + break; + case LEAVING: + handler.post(() -> { + if (!isDestroyed() && !isBeingDestroyed()) { + stopCallingSound(); + connectingTextView.setText(R.string.nc_leaving_call); + connectingView.setVisibility(View.VISIBLE); + conversationView.setVisibility(View.INVISIBLE); + progressBar.setVisibility(View.VISIBLE); + errorImageView.setVisibility(View.GONE); + } + }); + break; + default: + } + } + } + + private void playCallingSound() { + stopCallingSound(); + Uri ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/raw/librem_by_feandesign_call"); + if (getActivity() != null) { + mediaPlayer = new MediaPlayer(); + try { + mediaPlayer.setDataSource(Objects.requireNonNull(getActivity()), ringtoneUri); + mediaPlayer.setLooping(true); + AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes + .CONTENT_TYPE_SONIFICATION).setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build(); + mediaPlayer.setAudioAttributes(audioAttributes); + + mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start()); + + mediaPlayer.prepareAsync(); + + } catch (IOException e) { + Log.e(TAG, "Failed to play sound"); + } + } + } + + private void stopCallingSound() { + if (mediaPlayer != null) { + if (mediaPlayer.isPlaying()) { + mediaPlayer.stop(); + } + + mediaPlayer.release(); + mediaPlayer = null; + } + } + @Override protected void onAttach(@NonNull View view) { super.onAttach(view); @@ -1990,7 +2199,7 @@ public class CallController extends BaseController { handler.removeCallbacksAndMessages(null); } - currentCallStatus = CallStatus.RECONNECTING; + setCallState(CallStatus.RECONNECTING); hangupNetworkCalls(false); } else if (networkEvent.getNetworkConnectionEvent().equals(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED)) { @@ -1998,7 +2207,7 @@ public class CallController extends BaseController { handler.removeCallbacksAndMessages(null); } - currentCallStatus = CallStatus.OFFLINE; + setCallState(CallStatus.OFFLINE); hangup(false); } } diff --git a/app/src/main/res/drawable/ic_av_timer_timer_24dp.xml b/app/src/main/res/drawable/ic_av_timer_timer_24dp.xml new file mode 100644 index 000000000..3b2fa89c9 --- /dev/null +++ b/app/src/main/res/drawable/ic_av_timer_timer_24dp.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_signal_wifi_off_white_24dp.xml b/app/src/main/res/drawable/ic_signal_wifi_off_white_24dp.xml new file mode 100644 index 000000000..f0eed1606 --- /dev/null +++ b/app/src/main/res/drawable/ic_signal_wifi_off_white_24dp.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/layout/call_states.xml b/app/src/main/res/layout/call_states.xml new file mode 100644 index 000000000..6e46a77fd --- /dev/null +++ b/app/src/main/res/layout/call_states.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/controller_call.xml b/app/src/main/res/layout/controller_call.xml index 3c34ca12b..14a9c7d54 100644 --- a/app/src/main/res/layout/controller_call.xml +++ b/app/src/main/res/layout/controller_call.xml @@ -27,36 +27,10 @@ android:fitsSystemWindows="true" tools:context=".activities.MagicCallActivity"> - - - - - - - + android:layout_height="match_parent" /> Connecting… + Calling… Incoming call from Guest New public conversation Public conversations let you invite people from outside through a specially crafted link. + No response in 45 seconds, tap to try again + Reconnecting… + Currently offline, please check your connectivity + Leaving call… %1$s on %2$s notification channel @@ -266,4 +271,5 @@ Back Refresh %1$s | Last modified: %2$s +