Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-06-09 20:27:55 +02:00
parent c90ff879c2
commit 0f0119d22c
6 changed files with 360 additions and 60 deletions

View File

@ -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<GenericOverall>() {
@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<SignalingOverall>() {
@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);
}
}

View File

@ -0,0 +1,25 @@
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~
~ 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/>.
-->
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M11,17c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1zM11,3v4h2L13,5.08c3.39,0.49 6,3.39 6,6.92 0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-1.68 0.59,-3.22 1.58,-4.42L12,13l1.41,-1.41 -6.8,-6.8v0.02C4.42,6.45 3,9.05 3,12c0,4.97 4.02,9 9,9 4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9h-1zM18,12c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1 0.45,1 1,1 1,-0.45 1,-1zM6,12c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1z"/>
</vector>

View File

@ -0,0 +1,25 @@
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~
~ 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/>.
-->
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"/>
</vector>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~
~ 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/>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/connectingRelativeLayoutView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/grey950">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/item_height"
android:layout_height="@dimen/item_height"
android:layout_above="@id/connectingTextView"
android:layout_centerHorizontal="true"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:indeterminate="true"
android:indeterminateTint="@color/colorPrimary"
android:indeterminateTintMode="src_in" />
<ImageView
android:id="@+id/errorImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_above="@id/connectingTextView"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_signal_wifi_off_white_24dp"
android:visibility="gone" />
<TextView
android:id="@+id/connectingTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_margin="16dp"
android:gravity="center"
android:text="@string/nc_connecting_call"
android:textAlignment="center"
android:textColor="@color/white" />
</RelativeLayout>

View File

@ -27,36 +27,10 @@
android:fitsSystemWindows="true"
tools:context=".activities.MagicCallActivity">
<RelativeLayout
android:id="@+id/connectingRelativeLayoutView"
<include layout="@layout/call_states"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/item_height"
android:layout_height="@dimen/item_height"
android:layout_centerInParent="true"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:indeterminate="true"
android:indeterminateTint="@color/colorPrimary"
android:indeterminateTintMode="src_in" />
<TextView
android:id="@+id/connectingTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/progress_bar"
android:layout_centerHorizontal="true"
android:layout_margin="16dp"
android:text="@string/nc_connecting_call"
android:textAlignment="center"
android:textColor="@color/white" />
</RelativeLayout>
android:layout_height="match_parent" />
<RelativeLayout
android:id="@+id/conversationRelativeLayoutView"

View File

@ -168,11 +168,16 @@
<!-- Call -->
<string name="nc_connecting_call">Connecting…</string>
<string name="nc_calling">Calling…</string>
<string name="nc_incoming_call">Incoming call from</string>
<string name="nc_nick_guest">Guest</string>
<string name="nc_public_call">New public conversation</string>
<string name="nc_public_call_explanation">Public conversations let you invite people from outside through a
specially crafted link.</string>
<string name="nc_call_timeout">No response in 45 seconds, tap to try again</string>
<string name="nc_call_reconnecting">Reconnecting…</string>
<string name="nc_offline">Currently offline, please check your connectivity</string>
<string name="nc_leaving_call">Leaving call…</string>
<!-- Notification channels -->
<string name="nc_notification_channel">%1$s on %2$s notification channel</string>
@ -266,4 +271,5 @@
<string name="nc_file_browser_back">Back</string>
<string name="nc_file_browser_refresh">Refresh</string>
<string name="nc_last_modified">%1$s | Last modified: %2$s</string>
</resources>