diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt index babda90c6..0c0ec6dff 100644 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt @@ -41,7 +41,7 @@ import com.bluelinelabs.logansquare.LoganSquare import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.nextcloud.talk.R -import com.nextcloud.talk.activities.MagicCallActivity +import com.nextcloud.talk.activities.CallNotificationActivity import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication @@ -178,7 +178,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { ) } } else if (type == "call") { - val fullScreenIntent = Intent(applicationContext, MagicCallActivity::class.java) + val fullScreenIntent = Intent(applicationContext, CallNotificationActivity::class.java) val bundle = Bundle() bundle.putString(BundleKeys.KEY_ROOM_ID, decryptedPushMessage!!.id) bundle.putParcelable(KEY_USER_ENTITY, signatureVerification!!.userEntity) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e254e15ce..1d0260733 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -123,9 +123,24 @@ + android:name=".activities.CallActivity" + android:theme="@style/AppTheme.CallLauncher" + android:supportsPictureInPicture="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" + android:launchMode="singleTask" + android:taskAffinity=".call" + android:excludeFromRecents="true" + android:showOnLockScreen="true"/> + + . */ -package com.nextcloud.talk.controllers; +package com.nextcloud.talk.activities; import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Color; +import android.graphics.drawable.Icon; import android.media.AudioAttributes; import android.media.MediaPlayer; import android.net.Uri; @@ -36,28 +43,20 @@ import android.os.Looper; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; import android.widget.RelativeLayout; -import android.widget.TextView; import com.bluelinelabs.logansquare.LoganSquare; -import com.facebook.drawee.view.SimpleDraweeView; import com.nextcloud.talk.R; -import com.nextcloud.talk.activities.MagicCallActivity; import com.nextcloud.talk.adapters.ParticipantDisplayItem; import com.nextcloud.talk.adapters.ParticipantsAdapter; import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.controllers.base.BaseController; +import com.nextcloud.talk.databinding.CallActivityBinding; import com.nextcloud.talk.events.ConfigurationChangeEvent; import com.nextcloud.talk.events.MediaStreamEvent; import com.nextcloud.talk.events.NetworkEvent; @@ -122,7 +121,6 @@ import org.webrtc.PeerConnectionFactory; import org.webrtc.RendererCommon; import org.webrtc.SessionDescription; import org.webrtc.SurfaceTextureHelper; -import org.webrtc.SurfaceViewRenderer; import org.webrtc.VideoCapturer; import org.webrtc.VideoSource; import org.webrtc.VideoTrack; @@ -139,14 +137,12 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import autodagger.AutoInjector; -import butterknife.BindView; -import butterknife.OnClick; -import butterknife.OnItemClick; -import butterknife.OnLongClick; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -159,65 +155,7 @@ import okhttp3.Cache; import pub.devrel.easypermissions.AfterPermissionGranted; @AutoInjector(NextcloudTalkApplication.class) -public class CallController extends BaseController { - - private static final String TAG = "CallController"; - - private static final String[] PERMISSIONS_CALL = { - android.Manifest.permission.CAMERA, - android.Manifest.permission.RECORD_AUDIO, - }; - - private static final String[] PERMISSIONS_CAMERA = { - Manifest.permission.CAMERA - }; - - private static final String[] PERMISSIONS_MICROPHONE = { - Manifest.permission.RECORD_AUDIO - }; - - @BindView(R.id.callControlEnableSpeaker) - SimpleDraweeView callControlEnableSpeaker; - - @BindView(R.id.pip_video_view) - SurfaceViewRenderer pipVideoView; - @BindView(R.id.controllerCallLayout) - RelativeLayout controllerCallLayout; - @BindView(R.id.gridview) - GridView gridView; - - @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.callStateTextView) - TextView callStateTextView; - - @BindView(R.id.callInfosLinearLayout) - LinearLayout callInfosLinearLayout; - @BindView(R.id.callVoiceOrVideoTextView) - TextView callVoiceOrVideoTextView; - @BindView(R.id.callConversationNameTextView) - TextView callConversationNameTextView; - - @BindView(R.id.selfVideoView) - FrameLayout selfVideoView; - - @BindView(R.id.callStateRelativeLayoutView) - RelativeLayout callStateView; - - @BindView(R.id.conversationRelativeLayoutView) - RelativeLayout conversationView; - - @BindView(R.id.errorImageView) - ImageView errorImageView; - - @BindView(R.id.callStateProgressBar) - ProgressBar progressBar; +public class CallActivity extends CallBaseActivity { @Inject NcApi ncApi; @@ -230,6 +168,28 @@ public class CallController extends BaseController { @Inject Cache cache; + public static final String TAG = "CallActivity"; + + private static final String[] PERMISSIONS_CALL = { + android.Manifest.permission.CAMERA, + android.Manifest.permission.RECORD_AUDIO, + }; + + private static final String[] PERMISSIONS_CAMERA = { + Manifest.permission.CAMERA + }; + + private static final String[] PERMISSIONS_MICROPHONE = { + Manifest.permission.RECORD_AUDIO + }; + + private static final String MICROPHONE_PIP_INTENT_NAME = "microphone_pip_intent"; + private static final String MICROPHONE_PIP_INTENT_EXTRA_ACTION = "microphone_pip_action"; + private static final int MICROPHONE_PIP_REQUEST_MUTE = 1; + private static final int MICROPHONE_PIP_REQUEST_UNMUTE = 2; + + private BroadcastReceiver mReceiver; + private PeerConnectionFactory peerConnectionFactory; private MediaConstraints audioConstraints; private MediaConstraints videoConstraints; @@ -255,7 +215,7 @@ public class CallController extends BaseController { private Map participantMap = new HashMap<>(); private boolean videoOn = false; - private boolean audioOn = false; + private boolean microphoneOn = false; private boolean isVoiceOnlyCall; private boolean isIncomingCallFromNotification; @@ -290,89 +250,143 @@ public class CallController extends BaseController { private Map participantDisplayItems; private ParticipantsAdapter participantsAdapter; + private CallActivityBinding binding; + @Parcel public enum CallStatus { CONNECTING, CALLING_TIMEOUT, JOINED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING, PUBLISHER_FAILED } - public CallController(Bundle args) { - super(args); + @SuppressLint("ClickableViewAccessibility") + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); - roomId = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), ""); - 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); + binding = CallActivityBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); - if (args.containsKey(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL())) { - isIncomingCallFromNotification = args.getBoolean(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL()); + hideNavigationIfNoPipAvailable(); + + Bundle extras = getIntent().getExtras(); + roomId = extras.getString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), ""); + roomToken = extras.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), ""); + conversationUser = extras.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()); + conversationPassword = extras.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), ""); + conversationName = extras.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), ""); + isVoiceOnlyCall = extras.getBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false); + + if (extras.containsKey(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL())) { + isIncomingCallFromNotification = extras.getBoolean(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL()); } credentials = ApiUtils.getCredentials(conversationUser.getUsername(), conversationUser.getToken()); - baseUrl = args.getString(BundleKeys.INSTANCE.getKEY_MODIFIED_BASE_URL(), ""); - + baseUrl = extras.getString(BundleKeys.INSTANCE.getKEY_MODIFIED_BASE_URL(), ""); if (TextUtils.isEmpty(baseUrl)) { baseUrl = conversationUser.getBaseUrl(); } powerManagerUtils = new PowerManagerUtils(); - if (args.getString("state", "").equalsIgnoreCase("resume")) { + if (extras.getString("state", "").equalsIgnoreCase("resume")) { setCallState(CallStatus.IN_CONVERSATION); } else { setCallState(CallStatus.CONNECTING); } - } - @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_call, container, false); - } + initClickListeners(); + binding.microphoneButton.setOnTouchListener(new MicrophoneButtonTouchListener()); - private void createCameraEnumerator() { - if (getActivity() != null) { - boolean camera2EnumeratorIsSupported = false; - try { - camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(getActivity()); - } catch (final Throwable throwable) { - Log.w(TAG, "Camera2Enumator threw an error"); - } + pulseAnimation = PulseAnimation.create().with(binding.microphoneButton) + .setDuration(310) + .setRepeatCount(PulseAnimation.INFINITE) + .setRepeatMode(PulseAnimation.REVERSE); - if (camera2EnumeratorIsSupported) { - cameraEnumerator = new Camera2Enumerator(getActivity()); - } else { - cameraEnumerator = new Camera1Enumerator(MagicWebRTCUtils.shouldEnableVideoHardwareAcceleration()); - } + binding.callControls.setZ(100.0f); + basicInitialization(); + participantDisplayItems = new HashMap<>(); + initViews(); + if (!isConnectionEstablished()) { + initiateCall(); } + updateSelfVideoViewPosition(); } - @SuppressLint("ClickableViewAccessibility") @Override - protected void onViewBound(@NonNull View view) { - super.onViewBound(view); - - microphoneControlButton.setOnTouchListener(new MicrophoneButtonTouchListener()); - - pulseAnimation = PulseAnimation.create().with(microphoneControlButton) - .setDuration(310) - .setRepeatCount(PulseAnimation.INFINITE) - .setRepeatMode(PulseAnimation.REVERSE); - - + public void onStart() { + super.onStart(); try { cache.evictAll(); } catch (IOException e) { Log.e(TAG, "Failed to evict cache"); } + } - callControls.setZ(100.0f); - basicInitialization(); - initViews(); - initPipView(); - initiateCall(); + private void initClickListeners() { + binding.pictureInPictureButton.setOnClickListener(l -> enterPipMode()); + + binding.speakerButton.setOnClickListener(l -> { + if (audioManager != null) { + audioManager.toggleUseSpeakerphone(); + if (audioManager.isSpeakerphoneAutoOn()) { + binding.speakerButton.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_up_white_24dp); + } else { + binding.speakerButton.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_mute_white_24dp); + } + } + }); + + binding.microphoneButton.setOnClickListener(l -> onMicrophoneClick()); + binding.microphoneButton.setOnLongClickListener(l -> { + if (!microphoneOn) { + callControlHandler.removeCallbacksAndMessages(null); + callInfosHandler.removeCallbacksAndMessages(null); + cameraSwitchHandler.removeCallbacksAndMessages(null); + isPTTActive = true; + binding.callControls.setVisibility(View.VISIBLE); + if (!isVoiceOnlyCall) { + binding.switchSelfVideoButton.setVisibility(View.VISIBLE); + } + } + onMicrophoneClick(); + return true; + }); + + binding.cameraButton.setOnClickListener(l -> onCameraClick()); + + binding.hangupButton.setOnClickListener(l -> { + setCallState(CallStatus.LEAVING); + hangup(true); + }); + + binding.switchSelfVideoButton.setOnClickListener(l -> switchCamera()); + + binding.gridview.setOnItemClickListener((parent, view, position, id) -> animateCallControls(true, 0)); + + binding.callStates.callStateRelativeLayout.setOnClickListener(l -> { + if (currentCallStatus.equals(CallStatus.CALLING_TIMEOUT)) { + setCallState(CallStatus.RECONNECTING); + hangupNetworkCalls(false); + } + }); + } + + private void createCameraEnumerator() { + boolean camera2EnumeratorIsSupported = false; + try { + camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(this); + } catch (final Throwable throwable) { + Log.w(TAG, "Camera2Enumator threw an error"); + } + + if (camera2EnumeratorIsSupported) { + cameraEnumerator = new Camera2Enumerator(this); + } else { + cameraEnumerator = new Camera1Enumerator(MagicWebRTCUtils.shouldEnableVideoHardwareAcceleration()); + } } private void basicInitialization() { @@ -382,14 +396,14 @@ 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() - .setOptions(options) - .setVideoEncoderFactory(defaultVideoEncoderFactory) - .setVideoDecoderFactory(defaultVideoDecoderFactory) - .createPeerConnectionFactory(); + .setOptions(options) + .setVideoEncoderFactory(defaultVideoEncoderFactory) + .setVideoDecoderFactory(defaultVideoDecoderFactory) + .createPeerConnectionFactory(); //Create MediaConstraints - Will be useful for specifying video and audio constraints. audioConstraints = new MediaConstraints(); @@ -436,138 +450,156 @@ public class CallController extends BaseController { } private void handleFromNotification() { - int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[] {ApiUtils.APIv4, 1}); + int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1}); ncApi.getRooms(credentials, ApiUtils.getUrlForRooms(apiVersion, baseUrl)) - .retry(3) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .retry(3) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull RoomsOverall roomsOverall) { - for (Conversation conversation : roomsOverall.getOcs().getData()) { - if (roomId.equals(conversation.getRoomId())) { - roomToken = conversation.getToken(); - break; - } + @Override + public void onNext(@io.reactivex.annotations.NonNull RoomsOverall roomsOverall) { + for (Conversation conversation : roomsOverall.getOcs().getData()) { + if (roomId.equals(conversation.getRoomId())) { + roomToken = conversation.getToken(); + break; } - - checkPermissions(); } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - // unused atm - } + checkPermissions(); + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + // unused atm + } + + @Override + public void onComplete() { + // unused atm + } + }); } - @SuppressLint("ClickableViewAccessibility") private void initViews() { - participantDisplayItems = new HashMap<>(); + Log.d(TAG, "initViews"); + binding.callInfosLinearLayout.setVisibility(View.VISIBLE); + binding.selfVideoViewWrapper.setVisibility(View.VISIBLE); + + if (!isPipModePossible()) { + binding.pictureInPictureButton.setVisibility(View.GONE); + } if (isVoiceOnlyCall) { - callControlEnableSpeaker.setVisibility(View.VISIBLE); - cameraSwitchButton.setVisibility(View.GONE); - cameraControlButton.setVisibility(View.GONE); - pipVideoView.setVisibility(View.GONE); + binding.speakerButton.setVisibility(View.VISIBLE); + binding.switchSelfVideoButton.setVisibility(View.GONE); + binding.cameraButton.setVisibility(View.GONE); + binding.selfVideoRenderer.setVisibility(View.GONE); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.BELOW, R.id.callInfosLinearLayout); - int callControlsHeight = - Math.round(getApplicationContext().getResources().getDimension(R.dimen.call_controls_height)); - params.setMargins(0,0,0, callControlsHeight); - gridView.setLayoutParams(params); + int callControlsHeight = Math.round(getApplicationContext().getResources().getDimension(R.dimen.call_controls_height)); + params.setMargins(0, 0, 0, callControlsHeight); + binding.gridview.setLayoutParams(params); } else { - callControlEnableSpeaker.setVisibility(View.GONE); + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 0); + binding.gridview.setLayoutParams(params); + + binding.speakerButton.setVisibility(View.GONE); if (cameraEnumerator.getDeviceNames().length < 2) { - cameraSwitchButton.setVisibility(View.GONE); + binding.switchSelfVideoButton.setVisibility(View.GONE); } - - pipVideoView.init(rootEglBase.getEglBaseContext(), null); - pipVideoView.setZOrderMediaOverlay(true); - // disabled because it causes some devices to crash - pipVideoView.setEnableHardwareScaler(false); - pipVideoView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT); - - pipVideoView.setOnTouchListener(new SelfVideoTouchListener()); + initSelfVideoView(); } - gridView.setOnTouchListener(new View.OnTouchListener() { + binding.gridview.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent me) { int action = me.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { - showCallControls(); + animateCallControls(true, 0); } return false; } }); + animateCallControls(true, 0); + initGridAdapter(); } + @SuppressLint("ClickableViewAccessibility") + private void initSelfVideoView() { + try { + binding.selfVideoRenderer.init(rootEglBase.getEglBaseContext(), null); + } catch (IllegalStateException e) { + Log.d(TAG, "selfVideoRenderer already initialized", e); + } + + binding.selfVideoRenderer.setZOrderMediaOverlay(true); + // disabled because it causes some devices to crash + binding.selfVideoRenderer.setEnableHardwareScaler(false); + binding.selfVideoRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT); + binding.selfVideoRenderer.setOnTouchListener(new SelfVideoTouchListener()); + } + private void initGridAdapter() { - if (conversationView != null) { - GridView gridView = conversationView.findViewById(R.id.gridview); - - int columns; - int participantsInGrid = participantDisplayItems.size(); - if (getResources() != null && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - if (participantsInGrid > 2) { - columns = 2; - } else { - columns = 1; - } + Log.d(TAG, "initGridAdapter"); + int columns; + int participantsInGrid = participantDisplayItems.size(); + if (getResources() != null && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + if (participantsInGrid > 2) { + columns = 2; } else { - if (participantsInGrid > 2) { - columns = 3; - } else if (participantsInGrid > 1) { - columns = 2; - } else { - columns = 1; - } + columns = 1; } + } else { + if (participantsInGrid > 2) { + columns = 3; + } else if (participantsInGrid > 1) { + columns = 2; + } else { + columns = 1; + } + } - gridView.setNumColumns(columns); + binding.gridview.setNumColumns(columns); - RelativeLayout gridViewWrapper = conversationView.findViewById(R.id.conversationRelativeLayoutView); - gridViewWrapper.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - gridViewWrapper.getViewTreeObserver().removeOnGlobalLayoutListener(this); - int height = gridViewWrapper.getMeasuredHeight(); - gridView.setMinimumHeight(height); - } - }); + binding.conversationRelativeLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + binding.conversationRelativeLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + int height = binding.conversationRelativeLayout.getMeasuredHeight(); + binding.gridview.setMinimumHeight(height); + } + }); - LinearLayout callInfosLinearLayout = conversationView.findViewById(R.id.callInfosLinearLayout); - callInfosLinearLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - callInfosLinearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); - } - }); + binding.callInfosLinearLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + binding.callInfosLinearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + }); - participantsAdapter = new ParticipantsAdapter( - this.getActivity(), - participantDisplayItems, - gridViewWrapper, - callInfosLinearLayout, - columns, - isVoiceOnlyCall); - gridView.setAdapter(participantsAdapter); + participantsAdapter = new ParticipantsAdapter( + this, + participantDisplayItems, + binding.conversationRelativeLayout, + binding.callInfosLinearLayout, + columns, + isVoiceOnlyCall); + binding.gridview.setAdapter(participantsAdapter); + + if (isInPipMode) { + updateUiForPipMode(); } } @@ -575,7 +607,7 @@ public class CallController extends BaseController { private void checkPermissions() { if (isVoiceOnlyCall) { onMicrophoneClick(); - } else if (getActivity() != null) { + } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(PERMISSIONS_CALL, 100); } else { @@ -591,30 +623,29 @@ public class CallController extends BaseController { @AfterPermissionGranted(100) private void onPermissionsGranted() { - if (EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CALL)) { + if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) { if (!videoOn && !isVoiceOnlyCall) { onCameraClick(); } - if (!audioOn) { + if (!microphoneOn) { onMicrophoneClick(); } if (!isVoiceOnlyCall) { if (cameraEnumerator.getDeviceNames().length == 0) { - cameraControlButton.setVisibility(View.GONE); + binding.cameraButton.setVisibility(View.GONE); } if (cameraEnumerator.getDeviceNames().length > 1) { - cameraSwitchButton.setVisibility(View.VISIBLE); + binding.switchSelfVideoButton.setVisibility(View.VISIBLE); } } if (!isConnectionEstablished()) { fetchSignalingSettings(); } - } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(), - PERMISSIONS_CALL)) { + } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_CALL)) { checkIfSomeAreApproved(); } @@ -623,30 +654,30 @@ public class CallController extends BaseController { private void checkIfSomeAreApproved() { if (!isVoiceOnlyCall) { if (cameraEnumerator.getDeviceNames().length == 0) { - cameraControlButton.setVisibility(View.GONE); + binding.cameraButton.setVisibility(View.GONE); } if (cameraEnumerator.getDeviceNames().length > 1) { - cameraSwitchButton.setVisibility(View.VISIBLE); + binding.switchSelfVideoButton.setVisibility(View.VISIBLE); } - if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA)) { + if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) { if (!videoOn) { onCameraClick(); } } else { - cameraControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); - cameraControlButton.setAlpha(0.7f); - cameraSwitchButton.setVisibility(View.GONE); + binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.cameraButton.setAlpha(0.7f); + binding.switchSelfVideoButton.setVisibility(View.GONE); } } - if (EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_MICROPHONE)) { - if (!audioOn) { + if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) { + if (!microphoneOn) { onMicrophoneClick(); } } else { - microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); } if (!isConnectionEstablished()) { @@ -658,14 +689,14 @@ public class CallController extends BaseController { private void onPermissionsDenied() { if (!isVoiceOnlyCall) { if (cameraEnumerator.getDeviceNames().length == 0) { - cameraControlButton.setVisibility(View.GONE); + binding.cameraButton.setVisibility(View.GONE); } else if (cameraEnumerator.getDeviceNames().length == 1) { - cameraSwitchButton.setVisibility(View.GONE); + binding.switchSelfVideoButton.setVisibility(View.GONE); } } - if (getActivity() != null && (EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) || - EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_MICROPHONE))) { + if ((EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) || + EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE))) { checkIfSomeAreApproved(); } else if (!isConnectionEstablished()) { fetchSignalingSettings(); @@ -673,13 +704,13 @@ public class CallController extends BaseController { } private void onAudioManagerDevicesChanged( - final MagicAudioManager.AudioDevice device, final Set availableDevices) { + final MagicAudioManager.AudioDevice device, final Set availableDevices) { Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", " - + "selected: " + device); + + "selected: " + device); final boolean shouldDisableProximityLock = (device.equals(MagicAudioManager.AudioDevice.WIRED_HEADSET) - || device.equals(MagicAudioManager.AudioDevice.SPEAKER_PHONE) - || device.equals(MagicAudioManager.AudioDevice.BLUETOOTH)); + || device.equals(MagicAudioManager.AudioDevice.SPEAKER_PHONE) + || device.equals(MagicAudioManager.AudioDevice.BLUETOOTH)); if (shouldDisableProximityLock) { powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK); @@ -701,7 +732,7 @@ public class CallController extends BaseController { localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource); localMediaStream.addTrack(localVideoTrack); localVideoTrack.setEnabled(false); - localVideoTrack.addSink(pipVideoView); + localVideoTrack.addSink(binding.selfVideoRenderer); } private void microphoneInitialization() { @@ -722,7 +753,7 @@ public class CallController extends BaseController { Logging.d(TAG, "Creating front facing camera capturer."); VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); if (videoCapturer != null) { - pipVideoView.setMirror(true); + binding.selfVideoRenderer.setMirror(true); return videoCapturer; } } @@ -737,7 +768,7 @@ public class CallController extends BaseController { VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); if (videoCapturer != null) { - pipVideoView.setMirror(false); + binding.selfVideoRenderer.setMirror(false); return videoCapturer; } } @@ -746,75 +777,51 @@ public class CallController extends BaseController { return null; } - @OnLongClick(R.id.call_control_microphone) - boolean onMicrophoneLongClick() { - if (!audioOn) { - callControlHandler.removeCallbacksAndMessages(null); - callInfosHandler.removeCallbacksAndMessages(null); - cameraSwitchHandler.removeCallbacksAndMessages(null); - isPTTActive = true; - callControls.setVisibility(View.VISIBLE); - if (!isVoiceOnlyCall) { - cameraSwitchButton.setVisibility(View.VISIBLE); - } - } - - onMicrophoneClick(); - return true; - } - - @OnClick(R.id.callControlEnableSpeaker) - public void onEnableSpeakerphoneClick() { - if (audioManager != null) { - audioManager.toggleUseSpeakerphone(); - if (audioManager.isSpeakerphoneAutoOn()) { - callControlEnableSpeaker.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_up_white_24dp); - } else { - callControlEnableSpeaker.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_mute_white_24dp); - } - } - } - - @OnClick(R.id.call_control_microphone) public void onMicrophoneClick() { - if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_MICROPHONE)) { + if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) { - if (getActivity() != null && !appPreferences.getPushToTalkIntroShown()) { - spotlightView = new SpotlightView.Builder(getActivity()) - .introAnimationDuration(300) - .enableRevealAnimation(true) - .performClick(false) - .fadeinTextDuration(400) - .headingTvColor(getResources().getColor(R.color.colorPrimary)) - .headingTvSize(20) - .headingTvText(getResources().getString(R.string.nc_push_to_talk)) - .subHeadingTvColor(getResources().getColor(R.color.bg_default)) - .subHeadingTvSize(16) - .subHeadingTvText(getResources().getString(R.string.nc_push_to_talk_desc)) - .maskColor(Color.parseColor("#dc000000")) - .target(microphoneControlButton) - .lineAnimDuration(400) - .lineAndArcColor(getResources().getColor(R.color.colorPrimary)) - .enableDismissAfterShown(true) - .dismissOnBackPress(true) - .usageId("pushToTalk") - .show(); + if (!appPreferences.getPushToTalkIntroShown()) { + spotlightView = new SpotlightView.Builder(this) + .introAnimationDuration(300) + .enableRevealAnimation(true) + .performClick(false) + .fadeinTextDuration(400) + .headingTvColor(getResources().getColor(R.color.colorPrimary)) + .headingTvSize(20) + .headingTvText(getResources().getString(R.string.nc_push_to_talk)) + .subHeadingTvColor(getResources().getColor(R.color.bg_default)) + .subHeadingTvSize(16) + .subHeadingTvText(getResources().getString(R.string.nc_push_to_talk_desc)) + .maskColor(Color.parseColor("#dc000000")) + .target(binding.microphoneButton) + .lineAnimDuration(400) + .lineAndArcColor(getResources().getColor(R.color.colorPrimary)) + .enableDismissAfterShown(true) + .dismissOnBackPress(true) + .usageId("pushToTalk") + .show(); appPreferences.setPushToTalkIntroShown(true); } if (!isPTTActive) { - audioOn = !audioOn; + microphoneOn = !microphoneOn; - if (audioOn) { - microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); + if (microphoneOn) { + binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); + updatePictureInPictureActions(R.drawable.ic_mic_white_24px, + getResources().getString(R.string.nc_pip_microphone_mute), + MICROPHONE_PIP_REQUEST_MUTE); } else { - microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + updatePictureInPictureActions(R.drawable.ic_mic_off_white_24px, + getResources().getString(R.string.nc_pip_microphone_unmute), + MICROPHONE_PIP_REQUEST_UNMUTE); } - toggleMedia(audioOn, false); + toggleMedia(microphoneOn, false); } else { - microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); + binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); pulseAnimation.start(); toggleMedia(true, false); } @@ -823,13 +830,12 @@ public class CallController extends BaseController { fetchSignalingSettings(); } - } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(), - PERMISSIONS_MICROPHONE)) { + } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_MICROPHONE)) { // Microphone permission is permanently denied so we cannot request it normally. OpenAppDetailsDialogFragment.show( - R.string.nc_microphone_permission_permanently_denied, - R.string.nc_permissions_settings, (AppCompatActivity) getActivity()); + R.string.nc_microphone_permission_permanently_denied, + R.string.nc_permissions_settings, (AppCompatActivity) this); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(PERMISSIONS_MICROPHONE, 100); @@ -839,39 +845,26 @@ public class CallController extends BaseController { } } - @OnClick(R.id.callControlToggleChat) - void onToggleChatClick() { - ((MagicCallActivity) getActivity()).showChat(); - } - - @OnClick(R.id.callControlHangupView) - void onHangupClick() { - setCallState(CallStatus.LEAVING); - hangup(true); - } - - @OnClick(R.id.call_control_camera) public void onCameraClick() { - if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA)) { + if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) { videoOn = !videoOn; if (videoOn) { - cameraControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_white_24px); + binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_white_24px); if (cameraEnumerator.getDeviceNames().length > 1) { - cameraSwitchButton.setVisibility(View.VISIBLE); + binding.switchSelfVideoButton.setVisibility(View.VISIBLE); } } else { - cameraControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); - cameraSwitchButton.setVisibility(View.GONE); + binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.switchSelfVideoButton.setVisibility(View.GONE); } toggleMedia(videoOn, true); - } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(), - PERMISSIONS_CAMERA)) { + } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_CAMERA)) { // Camera permission is permanently denied so we cannot request it normally. OpenAppDetailsDialogFragment.show( - R.string.nc_camera_permission_permanently_denied, - R.string.nc_permissions_settings, (AppCompatActivity) getActivity()); + R.string.nc_camera_permission_permanently_denied, + R.string.nc_permissions_settings, (AppCompatActivity) this); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -883,14 +876,13 @@ public class CallController extends BaseController { } - @OnClick({R.id.call_control_switch_camera}) public void switchCamera() { CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) videoCapturer; if (cameraVideoCapturer != null) { cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() { @Override public void onCameraSwitchDone(boolean currentCameraIsFront) { - pipVideoView.setMirror(currentCameraIsFront); + binding.selfVideoRenderer.setMirror(currentCameraIsFront); } @Override @@ -906,11 +898,11 @@ public class CallController extends BaseController { if (video) { message = "videoOff"; if (enable) { - cameraControlButton.setAlpha(1.0f); + binding.cameraButton.setAlpha(1.0f); message = "videoOn"; startVideoCapture(); } else { - cameraControlButton.setAlpha(0.7f); + binding.cameraButton.setAlpha(0.7f); if (videoCapturer != null) { try { videoCapturer.stopCapture(); @@ -924,17 +916,17 @@ public class CallController extends BaseController { localMediaStream.videoTracks.get(0).setEnabled(enable); } if (enable) { - pipVideoView.setVisibility(View.VISIBLE); + binding.selfVideoRenderer.setVisibility(View.VISIBLE); } else { - pipVideoView.setVisibility(View.INVISIBLE); + binding.selfVideoRenderer.setVisibility(View.INVISIBLE); } } else { message = "audioOff"; if (enable) { message = "audioOn"; - microphoneControlButton.setAlpha(1.0f); + binding.microphoneButton.setAlpha(1.0f); } else { - microphoneControlButton.setAlpha(0.7f); + binding.microphoneButton.setAlpha(0.7f); } if (localMediaStream != null && localMediaStream.audioTracks.size() > 0) { @@ -974,16 +966,16 @@ public class CallController extends BaseController { cameraSwitchHandler.removeCallbacksAndMessages(null); alpha = 1.0f; duration = 1000; - if (callControls.getVisibility() != View.VISIBLE) { - callControls.setAlpha(0.0f); - callControls.setVisibility(View.VISIBLE); + if (binding.callControls.getVisibility() != View.VISIBLE) { + binding.callControls.setAlpha(0.0f); + binding.callControls.setVisibility(View.VISIBLE); - callInfosLinearLayout.setAlpha(0.0f); - callInfosLinearLayout.setVisibility(View.VISIBLE); + binding.callInfosLinearLayout.setAlpha(0.0f); + binding.callInfosLinearLayout.setVisibility(View.VISIBLE); - cameraSwitchButton.setAlpha(0.0f); + binding.switchSelfVideoButton.setAlpha(0.0f); if (videoOn) { - cameraSwitchButton.setVisibility(View.VISIBLE); + binding.switchSelfVideoButton.setVisibility(View.VISIBLE); } } else { callControlHandler.postDelayed(() -> animateCallControls(false, 0), 5000); @@ -994,91 +986,91 @@ public class CallController extends BaseController { duration = 1000; } - if (callControls != null) { - callControls.setEnabled(false); - callControls.animate() - .translationY(0) - .alpha(alpha) - .setDuration(duration) - .setStartDelay(startDelay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (callControls != null) { - if (!show) { - callControls.setVisibility(View.GONE); - if (spotlightView != null && spotlightView.getVisibility() != View.GONE) { - spotlightView.setVisibility(View.GONE); + if (binding.callControls != null) { + binding.callControls.setEnabled(false); + binding.callControls.animate() + .translationY(0) + .alpha(alpha) + .setDuration(duration) + .setStartDelay(startDelay) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (binding.callControls != null) { + if (!show) { + binding.callControls.setVisibility(View.GONE); + if (spotlightView != null && spotlightView.getVisibility() != View.GONE) { + spotlightView.setVisibility(View.GONE); + } + } else { + callControlHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (!isPTTActive) { + animateCallControls(false, 0); + } } - } else { - callControlHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (!isPTTActive) { - animateCallControls(false, 0); - } - } - }, 7500); - } - - callControls.setEnabled(true); + }, 7500); } + + binding.callControls.setEnabled(true); } - }); + } + }); } - 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); - } + if (binding.callInfosLinearLayout != null) { + binding.callInfosLinearLayout.setEnabled(false); + binding.callInfosLinearLayout.animate() + .translationY(0) + .alpha(alpha) + .setDuration(duration) + .setStartDelay(startDelay) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (binding.callInfosLinearLayout != null) { + if (!show) { + binding.callInfosLinearLayout.setVisibility(View.GONE); + } else { + callInfosHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (!isPTTActive) { + animateCallControls(false, 0); } - }, 7500); - } - - callInfosLinearLayout.setEnabled(true); + } + }, 7500); } + + binding.callInfosLinearLayout.setEnabled(true); } - }); + } + }); } - if (cameraSwitchButton != null) { - cameraSwitchButton.setEnabled(false); - cameraSwitchButton.animate() - .translationY(0) - .alpha(alpha) - .setDuration(duration) - .setStartDelay(startDelay) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (cameraSwitchButton != null) { - if (!show) { - cameraSwitchButton.setVisibility(View.GONE); - } - - cameraSwitchButton.setEnabled(true); + if (binding.switchSelfVideoButton != null) { + binding.switchSelfVideoButton.setEnabled(false); + binding.switchSelfVideoButton.animate() + .translationY(0) + .alpha(alpha) + .setDuration(duration) + .setStartDelay(startDelay) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (binding.switchSelfVideoButton != null) { + if (!show) { + binding.switchSelfVideoButton.setVisibility(View.GONE); } + + binding.switchSelfVideoButton.setEnabled(true); } - }); + } + }); } } @@ -1087,179 +1079,189 @@ public class CallController extends BaseController { @Override public void onDestroy() { if (!currentCallStatus.equals(CallStatus.LEAVING)) { - onHangupClick(); + setCallState(CallStatus.LEAVING); + hangup(true); } powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.IDLE); super.onDestroy(); } private void fetchSignalingSettings() { - int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[] {ApiUtils.APIv3, 2, 1}); + int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1}); ncApi.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl)) - .subscribeOn(Schedulers.io()) - .retry(3) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .subscribeOn(Schedulers.io()) + .retry(3) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull SignalingSettingsOverall signalingSettingsOverall) { - if (signalingSettingsOverall != null && signalingSettingsOverall.getOcs() != null && - signalingSettingsOverall.getOcs().getSettings() != null) { + @Override + public void onNext(@io.reactivex.annotations.NonNull SignalingSettingsOverall signalingSettingsOverall) { + if (signalingSettingsOverall != null && signalingSettingsOverall.getOcs() != null && + signalingSettingsOverall.getOcs().getSettings() != null) { + externalSignalingServer = new ExternalSignalingServer(); + + if (!TextUtils.isEmpty(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer()) && + !TextUtils.isEmpty(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket())) { externalSignalingServer = new ExternalSignalingServer(); + externalSignalingServer.setExternalSignalingServer(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer()); + externalSignalingServer.setExternalSignalingTicket(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket()); + hasExternalSignalingServer = true; + } else { + hasExternalSignalingServer = false; + } - if (!TextUtils.isEmpty(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer()) && - !TextUtils.isEmpty(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket())) { - externalSignalingServer = new ExternalSignalingServer(); - externalSignalingServer.setExternalSignalingServer(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer()); - externalSignalingServer.setExternalSignalingTicket(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket()); - hasExternalSignalingServer = true; - } else { - hasExternalSignalingServer = false; + if (!conversationUser.getUserId().equals("?")) { + try { + userUtils.createOrUpdateUser(null, null, null, null, null, null, null, + conversationUser.getId(), null, null, LoganSquare.serialize(externalSignalingServer)) + .subscribeOn(Schedulers.io()) + .subscribe(); + } catch (IOException exception) { + Log.e(TAG, "Failed to serialize external signaling server", exception); } + } else { + try { + conversationUser.setExternalSignalingServer(LoganSquare.serialize(externalSignalingServer)); + } catch (IOException exception) { + Log.e(TAG, "Failed to serialize external signaling server", exception); + } + } - if (!conversationUser.getUserId().equals("?")) { - try { - userUtils.createOrUpdateUser(null, null, null, null, null, null, null, - conversationUser.getId(), null, null, LoganSquare.serialize(externalSignalingServer)) - .subscribeOn(Schedulers.io()) - .subscribe(); - } catch (IOException exception) { - Log.e(TAG, "Failed to serialize external signaling server", exception); + if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) { + List stunServers = + signalingSettingsOverall.getOcs().getSettings().getStunServers(); + if (apiVersion == ApiUtils.APIv3) { + for (IceServer stunServer : stunServers) { + if (stunServer.getUrls() != null) { + for (String url : stunServer.getUrls()) { + iceServers.add(new PeerConnection.IceServer(url)); + } + } } } else { - try { - conversationUser.setExternalSignalingServer(LoganSquare.serialize(externalSignalingServer)); - } catch (IOException exception) { - Log.e(TAG, "Failed to serialize external signaling server", exception); - } - } - - if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) { - List stunServers = - signalingSettingsOverall.getOcs().getSettings().getStunServers(); - if (apiVersion == ApiUtils.APIv3) { + if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) { for (IceServer stunServer : stunServers) { - if (stunServer.getUrls() != null) { - for (String url : stunServer.getUrls()) { - iceServers.add(new PeerConnection.IceServer(url)); - } - } - } - } else { - if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) { - for (IceServer stunServer : stunServers) { - iceServers.add(new PeerConnection.IceServer(stunServer.getUrl())); - } - } - } - } - - if (signalingSettingsOverall.getOcs().getSettings().getTurnServers() != null) { - List turnServers = - signalingSettingsOverall.getOcs().getSettings().getTurnServers(); - for (IceServer turnServer : turnServers) { - if (turnServer.getUrls() != null) { - for (String url : turnServer.getUrls()) { - iceServers.add(new PeerConnection.IceServer( - url, turnServer.getUsername(), turnServer.getCredential() - )); - } + iceServers.add(new PeerConnection.IceServer(stunServer.getUrl())); } } } } - checkCapabilities(); + if (signalingSettingsOverall.getOcs().getSettings().getTurnServers() != null) { + List turnServers = + signalingSettingsOverall.getOcs().getSettings().getTurnServers(); + for (IceServer turnServer : turnServers) { + if (turnServer.getUrls() != null) { + for (String url : turnServer.getUrls()) { + iceServers.add(new PeerConnection.IceServer( + url, turnServer.getUsername(), turnServer.getCredential() + )); + } + } + } + } } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - Log.e(TAG, e.getMessage(), e); - } + checkCapabilities(); + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + Log.e(TAG, e.getMessage(), e); + } + + @Override + public void onComplete() { + // unused atm + } + }); } private void checkCapabilities() { ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl)) - .retry(3) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .retry(3) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull CapabilitiesOverall capabilitiesOverall) { - // FIXME check for compatible Call API version - if (hasExternalSignalingServer) { - setupAndInitiateWebSocketsConnection(); - } else { - joinRoomAndCall(); - } + @Override + public void onNext(@io.reactivex.annotations.NonNull CapabilitiesOverall capabilitiesOverall) { + // FIXME check for compatible Call API version + if (hasExternalSignalingServer) { + setupAndInitiateWebSocketsConnection(); + } else { + joinRoomAndCall(); } + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - // unused atm - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + // unused atm + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onComplete() { + // unused atm + } + }); } private void joinRoomAndCall() { callSession = ApplicationWideCurrentRoomHolder.getInstance().getSession(); - int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[] {ApiUtils.APIv4, 1}); + int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1}); + + Log.d(TAG, "joinRoomAndCall"); + Log.d(TAG, " baseUrl= " + baseUrl); + Log.d(TAG, " roomToken= " + roomToken); + Log.d(TAG, " callSession= " + callSession); + + String url = ApiUtils.getUrlForParticipantsActive(apiVersion, baseUrl, roomToken); + Log.d(TAG, " url= " + url); if (TextUtils.isEmpty(callSession)) { - ncApi.joinRoom(credentials, ApiUtils.getUrlForParticipantsActive(apiVersion, baseUrl, roomToken), - conversationPassword) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .retry(3) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + ncApi.joinRoom(credentials, url, conversationPassword) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .retry(3) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) { - callSession = roomOverall.getOcs().getData().getSessionId(); - ApplicationWideCurrentRoomHolder.getInstance().setSession(callSession); - ApplicationWideCurrentRoomHolder.getInstance().setCurrentRoomId(roomId); - ApplicationWideCurrentRoomHolder.getInstance().setCurrentRoomToken(roomToken); - ApplicationWideCurrentRoomHolder.getInstance().setUserInRoom(conversationUser); - callOrJoinRoomViaWebSocket(); - } + @Override + public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) { + callSession = roomOverall.getOcs().getData().getSessionId(); + Log.d(TAG, " new callSession by joinRoom= " + callSession); - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - // unused atm - } + ApplicationWideCurrentRoomHolder.getInstance().setSession(callSession); + ApplicationWideCurrentRoomHolder.getInstance().setCurrentRoomId(roomId); + ApplicationWideCurrentRoomHolder.getInstance().setCurrentRoomToken(roomToken); + ApplicationWideCurrentRoomHolder.getInstance().setUserInRoom(conversationUser); + callOrJoinRoomViaWebSocket(); + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + Log.e(TAG, "joinRoom onError", e); + } + + @Override + public void onComplete() { + Log.d(TAG, "joinRoom onComplete"); + } + }); } else { // we are in a room and start a call -> same session needs to be used callOrJoinRoomViaWebSocket(); @@ -1282,81 +1284,82 @@ public class CallController extends BaseController { inCallFlag = (int) Participant.ParticipantFlags.IN_CALL_WITH_AUDIO_AND_VIDEO.getValue(); } - int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[] {ApiUtils.APIv4, 1}); + int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1}); ncApi.joinCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken), inCallFlag) - .subscribeOn(Schedulers.io()) - .retry(3) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .subscribeOn(Schedulers.io()) + .retry(3) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) { - if (!currentCallStatus.equals(CallStatus.LEAVING)) { - setCallState(CallStatus.JOINED); + @Override + public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) { + if (!currentCallStatus.equals(CallStatus.LEAVING)) { + setCallState(CallStatus.JOINED); - ApplicationWideCurrentRoomHolder.getInstance().setInCall(true); + ApplicationWideCurrentRoomHolder.getInstance().setInCall(true); + ApplicationWideCurrentRoomHolder.getInstance().setDialing(false); - if (!TextUtils.isEmpty(roomToken)) { - NotificationUtils.INSTANCE.cancelExistingNotificationsForRoom(getApplicationContext(), - conversationUser, - roomToken); - } + if (!TextUtils.isEmpty(roomToken)) { + NotificationUtils.INSTANCE.cancelExistingNotificationsForRoom(getApplicationContext(), + conversationUser, + roomToken); + } - if (!hasExternalSignalingServer) { - int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, - new int[] {ApiUtils.APIv3, 2, 1}); + if (!hasExternalSignalingServer) { + int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, + new int[]{ApiUtils.APIv3, 2, 1}); - ncApi.pullSignalingMessages(credentials, - ApiUtils.getUrlForSignaling(apiVersion, - baseUrl, - roomToken)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .repeatWhen(observable -> observable) - .takeWhile(observable -> isConnectionEstablished()) - .retry(3, observable -> isConnectionEstablished()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - signalingDisposable = d; - } + ncApi.pullSignalingMessages(credentials, + ApiUtils.getUrlForSignaling(apiVersion, + baseUrl, + roomToken)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .repeatWhen(observable -> observable) + .takeWhile(observable -> isConnectionEstablished()) + .retry(3, observable -> isConnectionEstablished()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + signalingDisposable = d; + } - @Override - public void onNext( - @io.reactivex.annotations.NonNull - SignalingOverall signalingOverall) { - receivedSignalingMessages(signalingOverall.getOcs().getSignalings()); - } + @Override + public void onNext( + @io.reactivex.annotations.NonNull + SignalingOverall signalingOverall) { + receivedSignalingMessages(signalingOverall.getOcs().getSignalings()); + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - dispose(signalingDisposable); - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + dispose(signalingDisposable); + } - @Override - public void onComplete() { - dispose(signalingDisposable); - } - }); - } + @Override + public void onComplete() { + dispose(signalingDisposable); + } + }); } } + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - // unused atm - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + // unused atm + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onComplete() { + // unused atm + } + }); } private void setupAndInitiateWebSocketsConnection() { @@ -1366,9 +1369,9 @@ public class CallController extends BaseController { if (webSocketClient == null) { webSocketClient = WebSocketConnectionHelper.getExternalSignalingInstanceForServer( - externalSignalingServer.getExternalSignalingServer(), - conversationUser, externalSignalingServer.getExternalSignalingTicket(), - TextUtils.isEmpty(credentials)); + externalSignalingServer.getExternalSignalingServer(), + conversationUser, externalSignalingServer.getExternalSignalingTicket(), + TextUtils.isEmpty(credentials)); } else { if (webSocketClient.isConnected() && currentCallStatus.equals(CallStatus.PUBLISHER_FAILED)) { webSocketClient.restartWebSocket(); @@ -1408,27 +1411,22 @@ public class CallController extends BaseController { case "participantsUpdate": if (webSocketCommunicationEvent.getHashMap().get("roomToken").equals(roomToken)) { processUsersInRoom( - (List>) webSocketClient - .getJobWithId( - Integer.valueOf(webSocketCommunicationEvent.getHashMap().get("jobId")))); + (List>) webSocketClient + .getJobWithId( + Integer.valueOf(webSocketCommunicationEvent.getHashMap().get("jobId")))); } break; case "signalingMessage": processMessage((NCSignalingMessage) webSocketClient.getJobWithId( - Integer.valueOf(webSocketCommunicationEvent.getHashMap().get("jobId")))); + Integer.valueOf(webSocketCommunicationEvent.getHashMap().get("jobId")))); break; case "peerReadyForRequestingOffer": webSocketClient.requestOfferForSessionIdWithType( - webSocketCommunicationEvent.getHashMap().get("sessionId"), "video"); + webSocketCommunicationEvent.getHashMap().get("sessionId"), "video"); break; } } - @OnItemClick({R.id.gridview}) - public void showCallControls() { - animateCallControls(true, 0); - } - private void dispose(@Nullable Disposable disposable) { if (disposable != null && !disposable.isDisposed()) { disposable.dispose(); @@ -1473,8 +1471,8 @@ public class CallController extends BaseController { private void processMessage(NCSignalingMessage ncSignalingMessage) { if (ncSignalingMessage.getRoomType().equals("video") || ncSignalingMessage.getRoomType().equals("screen")) { MagicPeerConnectionWrapper magicPeerConnectionWrapper = - getPeerConnectionWrapperForSessionIdAndType(ncSignalingMessage.getFrom(), - ncSignalingMessage.getRoomType(), false); + getPeerConnectionWrapperForSessionIdAndType(ncSignalingMessage.getFrom(), + ncSignalingMessage.getRoomType(), false); String type = null; if (ncSignalingMessage.getPayload() != null && ncSignalingMessage.getPayload().getType() != null) { @@ -1494,16 +1492,16 @@ public class CallController extends BaseController { SessionDescription sessionDescriptionWithPreferredCodec; String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec - (ncSignalingMessage.getPayload().getSdp(), - "H264", false); + (ncSignalingMessage.getPayload().getSdp(), + "H264", false); sessionDescriptionWithPreferredCodec = new SessionDescription( - SessionDescription.Type.fromCanonicalForm(type), - sessionDescriptionStringWithPreferredCodec); + SessionDescription.Type.fromCanonicalForm(type), + sessionDescriptionStringWithPreferredCodec); if (magicPeerConnectionWrapper.getPeerConnection() != null) { magicPeerConnectionWrapper.getPeerConnection().setRemoteDescription(magicPeerConnectionWrapper - .getMagicSdpObserver(), sessionDescriptionWithPreferredCodec); + .getMagicSdpObserver(), sessionDescriptionWithPreferredCodec); } break; case "candidate": @@ -1540,8 +1538,8 @@ public class CallController extends BaseController { videoCapturer = null; } - if (pipVideoView != null) { - pipVideoView.release(); + if (binding.selfVideoRenderer != null) { + binding.selfVideoRenderer.release(); } if (audioSource != null) { @@ -1577,72 +1575,74 @@ public class CallController extends BaseController { } hangupNetworkCalls(shutDownView); + ApplicationWideCurrentRoomHolder.getInstance().setInCall(false); } private void hangupNetworkCalls(boolean shutDownView) { - int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[] {ApiUtils.APIv4, 1}); + int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1}); ncApi.leaveCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) { - if (shutDownView && getActivity() != null) { - getActivity().finish(); - } else if (!shutDownView && - (currentCallStatus == CallStatus.RECONNECTING || - currentCallStatus == CallStatus.PUBLISHER_FAILED)) { - initiateCall(); - } + @Override + public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) { + if (shutDownView) { + finish(); + } else if (!shutDownView && + (currentCallStatus == CallStatus.RECONNECTING || + currentCallStatus == CallStatus.PUBLISHER_FAILED)) { + initiateCall(); } + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - // unused atm - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + // unused atm + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onComplete() { + // unused atm + } + }); } + // TODO: why is this never called?! private void leaveRoom(boolean shutDownView) { - int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[] {ApiUtils.APIv4, 1}); + int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1}); ncApi.leaveRoom(credentials, ApiUtils.getUrlForParticipantsActive(apiVersion, baseUrl, roomToken)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) { - if (shutDownView && getActivity() != null) { - getActivity().finish(); - } + @Override + public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) { + if (shutDownView) { + finish(); } + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - // unused atm - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + // unused atm + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onComplete() { + // unused atm + } + }); } private void startVideoCapture() { @@ -1721,34 +1721,34 @@ public class CallController extends BaseController { private void getPeersForCall() { Log.d(TAG, "getPeersForCall"); - int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[] {ApiUtils.APIv4, 1}); + int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1}); ncApi.getPeersForCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken)) - .subscribeOn(Schedulers.io()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .subscribeOn(Schedulers.io()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull ParticipantsOverall participantsOverall) { - participantMap = new HashMap<>(); - for (Participant participant : participantsOverall.getOcs().getData()) { - participantMap.put(participant.getSessionId(), participant); - } + @Override + public void onNext(@io.reactivex.annotations.NonNull ParticipantsOverall participantsOverall) { + participantMap = new HashMap<>(); + for (Participant participant : participantsOverall.getOcs().getData()) { + participantMap.put(participant.getSessionId(), participant); } + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - Log.e(TAG, "error while executing getPeersForCall", e); - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + Log.e(TAG, "error while executing getPeersForCall", e); + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onComplete() { + // unused atm + } + }); } private void deleteMagicPeerConnection(MagicPeerConnectionWrapper magicPeerConnectionWrapper) { @@ -1839,16 +1839,12 @@ public class CallController extends BaseController { private void endPeerConnection(String sessionId, boolean justScreen) { List magicPeerConnectionWrappers; MagicPeerConnectionWrapper magicPeerConnectionWrapper; - if (!(magicPeerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty() - && getActivity() != null) { + if (!(magicPeerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty()) { for (int i = 0; i < magicPeerConnectionWrappers.size(); i++) { magicPeerConnectionWrapper = magicPeerConnectionWrappers.get(i); if (magicPeerConnectionWrapper.getSessionId().equals(sessionId)) { if (magicPeerConnectionWrapper.getVideoStreamType().equals("screen") || !justScreen) { - - - // TODO runOnUiThread not necessary??? - getActivity().runOnUiThread(() -> removeMediaStream(sessionId)); + runOnUiThread(() -> removeMediaStream(sessionId)); deleteMagicPeerConnection(magicPeerConnectionWrapper); } } @@ -1860,11 +1856,11 @@ public class CallController extends BaseController { Log.d(TAG, "removeMediaStream"); participantDisplayItems.remove(sessionId); - if (!isBeingDestroyed() && !isDestroyed()) { + if (!isDestroyed()) { initGridAdapter(); - if (callControls != null) { - callControls.setZ(100.0f); + if (binding.callControls != null) { + binding.callControls.setZ(100.0f); } } } @@ -1873,40 +1869,43 @@ public class CallController extends BaseController { public void onMessageEvent(ConfigurationChangeEvent configurationChangeEvent) { powerManagerUtils.setOrientation(Objects.requireNonNull(getResources()).getConfiguration().orientation); initGridAdapter(); - initPipView(); + updateSelfVideoViewPosition(); } - private void initPipView() { - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) pipVideoView.getLayoutParams(); + private void updateSelfVideoViewPosition() { + Log.d(TAG, "updateSelfVideoViewPosition"); + if (!isInPipMode) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) binding.selfVideoRenderer.getLayoutParams(); - DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics(); - int screenWidthPx = displayMetrics.widthPixels; + DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics(); + int screenWidthPx = displayMetrics.widthPixels; - int screenWidthDp = (int) DisplayUtils.convertPixelToDp(screenWidthPx, getApplicationContext()); + int screenWidthDp = (int) DisplayUtils.convertPixelToDp(screenWidthPx, getApplicationContext()); - float newXafterRotate = 0; - float newYafterRotate; - if (callInfosLinearLayout.getVisibility() == View.VISIBLE) { - newYafterRotate = 250; - } else { - newYafterRotate = 20; + float newXafterRotate = 0; + float newYafterRotate; + if (binding.callInfosLinearLayout.getVisibility() == View.VISIBLE) { + newYafterRotate = 250; + } else { + newYafterRotate = 20; + } + + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + layoutParams.height = (int) getResources().getDimension(R.dimen.large_preview_dimension); + layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.large_preview_dimension) * 0.8); + + } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams.width = (int) getResources().getDimension(R.dimen.large_preview_dimension); + newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.large_preview_dimension) * 0.5); + } + binding.selfVideoRenderer.setLayoutParams(layoutParams); + + int newXafterRotatePx = (int) DisplayUtils.convertDpToPixel(newXafterRotate, getApplicationContext()); + binding.selfVideoViewWrapper.setY(newYafterRotate); + binding.selfVideoViewWrapper.setX(newXafterRotatePx); } - - if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - layoutParams.height = (int) getResources().getDimension(R.dimen.large_preview_dimension); - layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; - newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.large_preview_dimension) * 0.8); - - } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; - layoutParams.width = (int) getResources().getDimension(R.dimen.large_preview_dimension); - newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.large_preview_dimension) * 0.5); - } - pipVideoView.setLayoutParams(layoutParams); - - int newXafterRotatePx = (int) DisplayUtils.convertDpToPixel(newXafterRotate, getApplicationContext()); - selfVideoView.setY(newYafterRotate); - selfVideoView.setX(newXafterRotatePx); } @Subscribe(threadMode = ThreadMode.MAIN) @@ -1914,19 +1913,19 @@ public class CallController extends BaseController { String sessionId = peerConnectionEvent.getSessionId(); if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType - .PEER_CLOSED)) { + .PEER_CLOSED)) { endPeerConnection(sessionId, peerConnectionEvent.getVideoStreamType().equals("screen")); } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent - .PeerConnectionEventType.SENSOR_FAR) || - peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent - .PeerConnectionEventType.SENSOR_NEAR)) { + .PeerConnectionEventType.SENSOR_FAR) || + peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent + .PeerConnectionEventType.SENSOR_NEAR)) { if (!isVoiceOnlyCall) { boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent - .PeerConnectionEventType.SENSOR_FAR) && videoOn; - if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) && - (currentCallStatus.equals(CallStatus.CONNECTING) || isConnectionEstablished()) && videoOn - && enableVideo != localVideoTrack.enabled()) { + .PeerConnectionEventType.SENSOR_FAR) && videoOn; + if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) && + (currentCallStatus.equals(CallStatus.CONNECTING) || isConnectionEstablished()) && videoOn + && enableVideo != localVideoTrack.enabled()) { toggleMedia(enableVideo, true); } } @@ -1967,30 +1966,30 @@ public class CallController extends BaseController { if (magicPeerConnectionWrapperList.get(i).isMCUPublisher()) { magicPeerConnectionWrapper = magicPeerConnectionWrapperList.get(i); Observable - .interval(1, TimeUnit.SECONDS) - .repeatUntil(() -> (!isConnectionEstablished() || isBeingDestroyed() || isDestroyed())) - .observeOn(Schedulers.io()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .interval(1, TimeUnit.SECONDS) + .repeatUntil(() -> (!isConnectionEstablished() || isDestroyed())) + .observeOn(Schedulers.io()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull Long aLong) { - magicPeerConnectionWrapper.sendNickChannelData(dataChannelMessage); - } + @Override + public void onNext(@io.reactivex.annotations.NonNull Long aLong) { + magicPeerConnectionWrapper.sendNickChannelData(dataChannelMessage); + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - // unused atm - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + // unused atm + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onComplete() { + // unused atm + } + }); break; } @@ -2001,19 +2000,19 @@ public class CallController extends BaseController { public void onMessageEvent(MediaStreamEvent mediaStreamEvent) { if (mediaStreamEvent.getMediaStream() != null) { boolean hasAtLeastOneVideoStream = mediaStreamEvent.getMediaStream().videoTracks != null - && mediaStreamEvent.getMediaStream().videoTracks.size() > 0; + && mediaStreamEvent.getMediaStream().videoTracks.size() > 0; setupVideoStreamForLayout( - mediaStreamEvent.getMediaStream(), - mediaStreamEvent.getSession(), - hasAtLeastOneVideoStream, - mediaStreamEvent.getVideoStreamType()); + mediaStreamEvent.getMediaStream(), + mediaStreamEvent.getSession(), + hasAtLeastOneVideoStream, + mediaStreamEvent.getVideoStreamType()); } else { setupVideoStreamForLayout( - null, - mediaStreamEvent.getSession(), - false, - mediaStreamEvent.getVideoStreamType()); + null, + mediaStreamEvent.getSession(), + false, + mediaStreamEvent.getVideoStreamType()); } } @@ -2045,46 +2044,46 @@ public class CallController extends BaseController { if (!hasExternalSignalingServer) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("{") - .append("\"fn\":\"") - .append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper.getSignalingMessage()))).append("\"") - .append(",") - .append("\"sessionId\":") - .append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"") - .append(",") - .append("\"ev\":\"message\"") - .append("}"); + .append("\"fn\":\"") + .append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper.getSignalingMessage()))).append("\"") + .append(",") + .append("\"sessionId\":") + .append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"") + .append(",") + .append("\"ev\":\"message\"") + .append("}"); List strings = new ArrayList<>(); String stringToSend = stringBuilder.toString(); strings.add(stringToSend); - int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[] {ApiUtils.APIv3, 2, 1}); + int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1}); ncApi.sendSignalingMessages(credentials, ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken), strings.toString()) - .retry(3) - .subscribeOn(Schedulers.io()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - // unused atm - } + .retry(3) + .subscribeOn(Schedulers.io()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + // unused atm + } - @Override - public void onNext(@io.reactivex.annotations.NonNull SignalingOverall signalingOverall) { - receivedSignalingMessages(signalingOverall.getOcs().getSignalings()); - } + @Override + public void onNext(@io.reactivex.annotations.NonNull SignalingOverall signalingOverall) { + receivedSignalingMessages(signalingOverall.getOcs().getSignalings()); + } - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - Log.e(TAG, "", e); - } + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + Log.e(TAG, "", e); + } - @Override - public void onComplete() { - // unused atm - } - }); + @Override + public void onComplete() { + // unused atm + } + }); } else { webSocketClient.sendCallMessage(ncMessageWrapper); } @@ -2136,15 +2135,7 @@ public class CallController extends BaseController { participantDisplayItems.put(session, participantDisplayItem); initGridAdapter(); - callControls.setZ(100.0f); - } - - @OnClick(R.id.callStateRelativeLayoutView) - public void onConnectingViewClick() { - if (currentCallStatus.equals(CallStatus.CALLING_TIMEOUT)) { - setCallState(CallStatus.RECONNECTING); - hangupNetworkCalls(false); - } + binding.callControls.setZ(100.0f); } private void setCallState(CallStatus callState) { @@ -2161,105 +2152,105 @@ public class CallController extends BaseController { handler.post(() -> { playCallingSound(); if (isIncomingCallFromNotification) { - callStateTextView.setText(R.string.nc_call_incoming); + binding.callStates.callStateTextView.setText(R.string.nc_call_incoming); } else { - callStateTextView.setText(R.string.nc_call_ringing); + binding.callStates.callStateTextView.setText(R.string.nc_call_ringing); } - callConversationNameTextView.setText(conversationName); + binding.callConversationNameTextView.setText(conversationName); - callVoiceOrVideoTextView.setText(getDescriptionForCallType()); + binding.callModeTextView.setText(getDescriptionForCallType()); - if (callStateView.getVisibility() != View.VISIBLE) { - callStateView.setVisibility(View.VISIBLE); + if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) { + binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE); } - if (gridView.getVisibility() != View.INVISIBLE) { - gridView.setVisibility(View.INVISIBLE); + if (binding.gridview.getVisibility() != View.INVISIBLE) { + binding.gridview.setVisibility(View.INVISIBLE); } - if (progressBar.getVisibility() != View.VISIBLE) { - progressBar.setVisibility(View.VISIBLE); + if (binding.callStates.callStateProgressBar.getVisibility() != View.VISIBLE) { + binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE); } - if (errorImageView.getVisibility() != View.GONE) { - errorImageView.setVisibility(View.GONE); + if (binding.callStates.errorImageView.getVisibility() != View.GONE) { + binding.callStates.errorImageView.setVisibility(View.GONE); } }); break; case CALLING_TIMEOUT: handler.post(() -> { hangup(false); - callStateTextView.setText(R.string.nc_call_timeout); - callVoiceOrVideoTextView.setText(getDescriptionForCallType()); - if (callStateView.getVisibility() != View.VISIBLE) { - callStateView.setVisibility(View.VISIBLE); + binding.callStates.callStateTextView.setText(R.string.nc_call_timeout); + binding.callModeTextView.setText(getDescriptionForCallType()); + if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) { + binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE); } - if (progressBar.getVisibility() != View.GONE) { - progressBar.setVisibility(View.GONE); + if (binding.callStates.callStateProgressBar.getVisibility() != View.GONE) { + binding.callStates.callStateProgressBar.setVisibility(View.GONE); } - if (gridView.getVisibility() != View.INVISIBLE) { - gridView.setVisibility(View.INVISIBLE); + if (binding.gridview.getVisibility() != View.INVISIBLE) { + binding.gridview.setVisibility(View.INVISIBLE); } - errorImageView.setImageResource(R.drawable.ic_av_timer_timer_24dp); + binding.callStates.errorImageView.setImageResource(R.drawable.ic_av_timer_timer_24dp); - if (errorImageView.getVisibility() != View.VISIBLE) { - errorImageView.setVisibility(View.VISIBLE); + if (binding.callStates.errorImageView.getVisibility() != View.VISIBLE) { + binding.callStates.errorImageView.setVisibility(View.VISIBLE); } }); break; case RECONNECTING: handler.post(() -> { playCallingSound(); - callStateTextView.setText(R.string.nc_call_reconnecting); - callVoiceOrVideoTextView.setText(getDescriptionForCallType()); - if (callStateView.getVisibility() != View.VISIBLE) { - callStateView.setVisibility(View.VISIBLE); + binding.callStates.callStateTextView.setText(R.string.nc_call_reconnecting); + binding.callModeTextView.setText(getDescriptionForCallType()); + if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) { + binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE); } - if (gridView.getVisibility() != View.INVISIBLE) { - gridView.setVisibility(View.INVISIBLE); + if (binding.gridview.getVisibility() != View.INVISIBLE) { + binding.gridview.setVisibility(View.INVISIBLE); } - if (progressBar.getVisibility() != View.VISIBLE) { - progressBar.setVisibility(View.VISIBLE); + if (binding.callStates.callStateProgressBar.getVisibility() != View.VISIBLE) { + binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE); } - if (errorImageView.getVisibility() != View.GONE) { - errorImageView.setVisibility(View.GONE); + if (binding.callStates.errorImageView.getVisibility() != View.GONE) { + binding.callStates.errorImageView.setVisibility(View.GONE); } }); break; case JOINED: handler.postDelayed(() -> setCallState(CallStatus.CALLING_TIMEOUT), 45000); handler.post(() -> { - callVoiceOrVideoTextView.setText(getDescriptionForCallType()); - if (callStateView != null) { + binding.callModeTextView.setText(getDescriptionForCallType()); + if (binding.callStates.callStateRelativeLayout != null) { if (isIncomingCallFromNotification) { - callStateTextView.setText(R.string.nc_call_incoming); + binding.callStates.callStateTextView.setText(R.string.nc_call_incoming); } else { - callStateTextView.setText(R.string.nc_call_ringing); + binding.callStates.callStateTextView.setText(R.string.nc_call_ringing); } - if (callStateView.getVisibility() != View.VISIBLE) { - callStateView.setVisibility(View.VISIBLE); + if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) { + binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE); } } - if (progressBar != null) { - if (progressBar.getVisibility() != View.VISIBLE) { - progressBar.setVisibility(View.VISIBLE); + if (binding.callStates.callStateProgressBar != null) { + if (binding.callStates.callStateProgressBar.getVisibility() != View.VISIBLE) { + binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE); } } - if (gridView != null) { - if (gridView.getVisibility() != View.INVISIBLE) { - gridView.setVisibility(View.INVISIBLE); + if (binding.gridview != null) { + if (binding.gridview.getVisibility() != View.INVISIBLE) { + binding.gridview.setVisibility(View.INVISIBLE); } } - if (errorImageView != null) { - if (errorImageView.getVisibility() != View.GONE) { - errorImageView.setVisibility(View.GONE); + if (binding.callStates.errorImageView != null) { + if (binding.callStates.errorImageView.getVisibility() != View.GONE) { + binding.callStates.errorImageView.setVisibility(View.GONE); } } }); @@ -2267,37 +2258,37 @@ public class CallController extends BaseController { case IN_CONVERSATION: handler.post(() -> { stopCallingSound(); - callVoiceOrVideoTextView.setText(getDescriptionForCallType()); + binding.callModeTextView.setText(getDescriptionForCallType()); if (!isVoiceOnlyCall) { - callInfosLinearLayout.setVisibility(View.GONE); + binding.callInfosLinearLayout.setVisibility(View.GONE); } if (!isPTTActive) { animateCallControls(false, 5000); } - if (callStateView != null) { - if (callStateView.getVisibility() != View.INVISIBLE) { - callStateView.setVisibility(View.INVISIBLE); + if (binding.callStates.callStateRelativeLayout != null) { + if (binding.callStates.callStateRelativeLayout.getVisibility() != View.INVISIBLE) { + binding.callStates.callStateRelativeLayout.setVisibility(View.INVISIBLE); } } - if (progressBar != null) { - if (progressBar.getVisibility() != View.GONE) { - progressBar.setVisibility(View.GONE); + if (binding.callStates.callStateProgressBar != null) { + if (binding.callStates.callStateProgressBar.getVisibility() != View.GONE) { + binding.callStates.callStateProgressBar.setVisibility(View.GONE); } } - if (gridView != null) { - if (gridView.getVisibility() != View.VISIBLE) { - gridView.setVisibility(View.VISIBLE); + if (binding.gridview != null) { + if (binding.gridview.getVisibility() != View.VISIBLE) { + binding.gridview.setVisibility(View.VISIBLE); } } - if (errorImageView != null) { - if (errorImageView.getVisibility() != View.GONE) { - errorImageView.setVisibility(View.GONE); + if (binding.callStates.errorImageView != null) { + if (binding.callStates.errorImageView.getVisibility() != View.GONE) { + binding.callStates.errorImageView.setVisibility(View.GONE); } } }); @@ -2306,45 +2297,45 @@ public class CallController extends BaseController { handler.post(() -> { stopCallingSound(); - if (callStateTextView != null) { - callStateTextView.setText(R.string.nc_offline); + if (binding.callStates.callStateTextView != null) { + binding.callStates.callStateTextView.setText(R.string.nc_offline); - if (callStateView.getVisibility() != View.VISIBLE) { - callStateView.setVisibility(View.VISIBLE); + if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) { + binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE); } } - if (gridView != null) { - if (gridView.getVisibility() != View.INVISIBLE) { - gridView.setVisibility(View.INVISIBLE); + if (binding.gridview != null) { + if (binding.gridview.getVisibility() != View.INVISIBLE) { + binding.gridview.setVisibility(View.INVISIBLE); } } - if (progressBar != null) { - if (progressBar.getVisibility() != View.GONE) { - progressBar.setVisibility(View.GONE); + if (binding.callStates.callStateProgressBar != null) { + if (binding.callStates.callStateProgressBar.getVisibility() != View.GONE) { + binding.callStates.callStateProgressBar.setVisibility(View.GONE); } } - if (errorImageView != null) { - errorImageView.setImageResource(R.drawable.ic_signal_wifi_off_white_24dp); - if (errorImageView.getVisibility() != View.VISIBLE) { - errorImageView.setVisibility(View.VISIBLE); + if (binding.callStates.errorImageView != null) { + binding.callStates.errorImageView.setImageResource(R.drawable.ic_signal_wifi_off_white_24dp); + if (binding.callStates.errorImageView.getVisibility() != View.VISIBLE) { + binding.callStates.errorImageView.setVisibility(View.VISIBLE); } } }); break; case LEAVING: handler.post(() -> { - if (!isDestroyed() && !isBeingDestroyed()) { + if (!isDestroyed()) { stopCallingSound(); - callVoiceOrVideoTextView.setText(getDescriptionForCallType()); - callStateTextView.setText(R.string.nc_leaving_call); - callStateView.setVisibility(View.VISIBLE); - gridView.setVisibility(View.INVISIBLE); - progressBar.setVisibility(View.VISIBLE); - errorImageView.setVisibility(View.GONE); + binding.callModeTextView.setText(getDescriptionForCallType()); + binding.callStates.callStateTextView.setText(R.string.nc_leaving_call); + binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE); + binding.gridview.setVisibility(View.INVISIBLE); + binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE); + binding.callStates.errorImageView.setVisibility(View.GONE); } }); break; @@ -2367,29 +2358,28 @@ public class CallController extends BaseController { Uri ringtoneUri; if (isIncomingCallFromNotification) { ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + - "/raw/librem_by_feandesign_call"); + "/raw/librem_by_feandesign_call"); } else { ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/raw" + - "/tr110_1_kap8_3_freiton1"); + "/tr110_1_kap8_3_freiton1"); } - 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 = new MediaPlayer(); + try { + mediaPlayer.setDataSource(this, ringtoneUri); + mediaPlayer.setLooping(true); + AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType( + AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build(); + mediaPlayer.setAudioAttributes(audioAttributes); - mediaPlayer.prepareAsync(); + mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start()); - } catch (IOException e) { - Log.e(TAG, "Failed to play sound"); - } + mediaPlayer.prepareAsync(); + + } catch (IOException e) { + Log.e(TAG, "Failed to play sound"); } } @@ -2404,18 +2394,6 @@ public class CallController extends BaseController { } } - @Override - protected void onAttach(@NonNull View view) { - super.onAttach(view); - eventBus.register(this); - } - - @Override - protected void onDetach(@NonNull View view) { - super.onDetach(view); - eventBus.unregister(this); - } - private class MicrophoneButtonTouchListener implements View.OnTouchListener { @SuppressLint("ClickableViewAccessibility") @@ -2424,7 +2402,7 @@ public class CallController extends BaseController { v.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP && isPTTActive) { isPTTActive = false; - microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); pulseAnimation.stop(); toggleMedia(false, false); animateCallControls(false, 5000); @@ -2436,18 +2414,125 @@ public class CallController extends BaseController { @Subscribe(threadMode = ThreadMode.BACKGROUND) public void onMessageEvent(NetworkEvent networkEvent) { if (networkEvent.getNetworkConnectionEvent() - .equals(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED)) { + .equals(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED)) { if (handler != null) { handler.removeCallbacksAndMessages(null); } } else if (networkEvent.getNetworkConnectionEvent() - .equals(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED)) { + .equals(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED)) { if (handler != null) { handler.removeCallbacksAndMessages(null); } } } + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); + Log.d(TAG, "onPictureInPictureModeChanged"); + Log.d(TAG, "isInPictureInPictureMode= " + isInPictureInPictureMode); + isInPipMode = isInPictureInPictureMode; + if (isInPictureInPictureMode) { + mReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null || !MICROPHONE_PIP_INTENT_NAME.equals(intent.getAction())) { + return; + } + + final int action = intent.getIntExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, 0); + switch (action) { + case MICROPHONE_PIP_REQUEST_MUTE: + case MICROPHONE_PIP_REQUEST_UNMUTE: + onMicrophoneClick(); + break; + } + } + }; + registerReceiver(mReceiver, new IntentFilter(MICROPHONE_PIP_INTENT_NAME)); + + updateUiForPipMode(); + } else { + unregisterReceiver(mReceiver); + mReceiver = null; + + updateUiForNormalMode(); + } + } + + void updatePictureInPictureActions( + @DrawableRes int iconId, + String title, + int requestCode) { + + if (isGreaterEqualOreo() && isPipModePossible()) { + final ArrayList actions = new ArrayList<>(); + + final Icon icon = Icon.createWithResource(this, iconId); + final PendingIntent intent = + PendingIntent.getBroadcast( + this, + requestCode, + new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode), + 0); + + actions.add(new RemoteAction(icon, title, title, intent)); + + mPictureInPictureParamsBuilder.setActions(actions); + setPictureInPictureParams(mPictureInPictureParamsBuilder.build()); + } + } + + public void updateUiForPipMode() { + Log.d(TAG, "updateUiForPipMode"); + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 0); + binding.gridview.setLayoutParams(params); + + + binding.callControls.setVisibility(View.GONE); + binding.callInfosLinearLayout.setVisibility(View.GONE); + binding.selfVideoViewWrapper.setVisibility(View.GONE); + binding.callStates.callStateRelativeLayout.setVisibility(View.GONE); + + if (participantDisplayItems.size() > 1) { + binding.pipCallConversationNameTextView.setText(conversationName); + binding.pipGroupCallOverlay.setVisibility(View.VISIBLE); + } else { + binding.pipGroupCallOverlay.setVisibility(View.INVISIBLE); + } + + binding.selfVideoRenderer.release(); + } + + public void updateUiForNormalMode() { + Log.d(TAG, "updateUiForNormalMode"); + if (isVoiceOnlyCall) { + binding.callControls.setVisibility(View.VISIBLE); + } else { + binding.callControls.setVisibility(View.INVISIBLE); // animateCallControls needs this to be invisible for a check. + } + initViews(); + + binding.callInfosLinearLayout.setVisibility(View.VISIBLE); + binding.selfVideoViewWrapper.setVisibility(View.VISIBLE); + + binding.pipGroupCallOverlay.setVisibility(View.INVISIBLE); + } + + @Override + void suppressFitsSystemWindows() { + binding.controllerCallLayout.setFitsSystemWindows(false); + } + + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + eventBus.post(new ConfigurationChangeEvent()); + } + private class SelfVideoTouchListener implements View.OnTouchListener { @SuppressLint("ClickableViewAccessibility") @@ -2456,10 +2541,10 @@ public class CallController extends BaseController { long duration = event.getEventTime() - event.getDownTime(); if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { - float newY = event.getRawY() - selfVideoView.getHeight() / (float) 2; - float newX = event.getRawX() - selfVideoView.getWidth() / (float) 2; - selfVideoView.setY(newY); - selfVideoView.setX(newX); + float newY = event.getRawY() - binding.selfVideoViewWrapper.getHeight() / (float) 2; + float newX = event.getRawX() - binding.selfVideoViewWrapper.getWidth() / (float) 2; + binding.selfVideoViewWrapper.setY(newY); + binding.selfVideoViewWrapper.setX(newX); } else if (event.getActionMasked() == MotionEvent.ACTION_UP && duration < 100) { switchCamera(); } diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java new file mode 100644 index 000000000..66da91360 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java @@ -0,0 +1,131 @@ +package com.nextcloud.talk.activities; + +import android.annotation.SuppressLint; +import android.app.AppOpsManager; +import android.app.KeyguardManager; +import android.app.PictureInPictureParams; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.util.Rational; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import com.nextcloud.talk.BuildConfig; + +public abstract class CallBaseActivity extends BaseActivity { + + public static final String TAG = "CallBaseActivity"; + + public PictureInPictureParams.Builder mPictureInPictureParamsBuilder; + public Boolean isInPipMode = false; + + @SuppressLint("ClickableViewAccessibility") + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + dismissKeyguard(); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + if (isGreaterEqualOreo() && isPipModePossible()) { + mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder(); + } + } + + void hideNavigationIfNoPipAvailable(){ + if (!isPipModePossible()) { + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + suppressFitsSystemWindows(); + } + } + + void dismissKeyguard() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(true); + setTurnScreenOn(true); + KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + keyguardManager.requestDismissKeyguard(this, null); + } else { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + } + } + + void enableKeyguard() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(false); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + } + } + + @Override + public void onStop() { + super.onStop(); + if (isInPipMode) { + finish(); + } + } + + @Override + public void onBackPressed() { + if (isPipModePossible()) { + enterPipMode(); + } + } + + @Override + protected void onUserLeaveHint() { + enterPipMode(); + } + + void enterPipMode() { + enableKeyguard(); + if (isGreaterEqualOreo() && isPipModePossible()) { + Rational pipRatio = new Rational(300, 500); + mPictureInPictureParamsBuilder.setAspectRatio(pipRatio); + enterPictureInPictureMode(mPictureInPictureParamsBuilder.build()); + } else { + // we don't support other solutions than PIP to have a call in the background. + // If PIP is not available the call is ended when user presses the home button. + Log.d(TAG, "Activity was finished because PIP is not available."); + finish(); + } + } + + boolean isPipModePossible() { + if (isGreaterEqualOreo()) { + boolean deviceHasPipFeature = getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + + AppOpsManager appOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); + boolean isPipFeatureGranted = appOpsManager.checkOpNoThrow( + AppOpsManager.OPSTR_PICTURE_IN_PICTURE, + android.os.Process.myUid(), + BuildConfig.APPLICATION_ID) == AppOpsManager.MODE_ALLOWED; + return deviceHasPipFeature && isPipFeatureGranted; + } + return false; + } + + boolean isGreaterEqualOreo(){ + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + + abstract void updateUiForPipMode(); + + abstract void updateUiForNormalMode(); + + abstract void suppressFitsSystemWindows(); +} diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.java new file mode 100644 index 000000000..7c126e4d6 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.java @@ -0,0 +1,458 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +package com.nextcloud.talk.activities; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +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.text.TextUtils; +import android.util.Log; +import android.view.View; + +import com.bluelinelabs.logansquare.LoganSquare; +import com.facebook.common.executors.UiThreadImmediateExecutorService; +import com.facebook.common.references.CloseableReference; +import com.facebook.datasource.DataSource; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.imagepipeline.core.ImagePipeline; +import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; +import com.facebook.imagepipeline.image.CloseableImage; +import com.facebook.imagepipeline.request.ImageRequest; +import com.nextcloud.talk.R; +import com.nextcloud.talk.api.NcApi; +import com.nextcloud.talk.application.NextcloudTalkApplication; +import com.nextcloud.talk.databinding.CallNotificationActivityBinding; +import com.nextcloud.talk.events.CallNotificationClick; +import com.nextcloud.talk.models.RingtoneSettings; +import com.nextcloud.talk.models.database.CapabilitiesUtil; +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.participants.Participant; +import com.nextcloud.talk.models.json.participants.ParticipantsOverall; +import com.nextcloud.talk.utils.ApiUtils; +import com.nextcloud.talk.utils.DisplayUtils; +import com.nextcloud.talk.utils.DoNotDisturbUtils; +import com.nextcloud.talk.utils.bundle.BundleKeys; +import com.nextcloud.talk.utils.preferences.AppPreferences; + +import org.greenrobot.eventbus.EventBus; +import org.parceler.Parcels; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import autodagger.AutoInjector; +import butterknife.OnClick; +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import okhttp3.Cache; + +@SuppressLint("LongLogTag") +@AutoInjector(NextcloudTalkApplication.class) +public class CallNotificationActivity extends CallBaseActivity { + + public static final String TAG = "CallNotificationActivity"; + + @Inject + NcApi ncApi; + + @Inject + AppPreferences appPreferences; + + @Inject + Cache cache; + + @Inject + EventBus eventBus; + + @Inject + Context context; + + private List disposablesList = new ArrayList<>(); + private Bundle originalBundle; + private String roomId; + private UserEntity userBeingCalled; + private String credentials; + private Conversation currentConversation; + private MediaPlayer mediaPlayer; + private boolean leavingScreen = false; + private Handler handler; + private CallNotificationActivityBinding binding; + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + + NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); + + binding = CallNotificationActivityBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + hideNavigationIfNoPipAvailable(); + + eventBus.post(new CallNotificationClick()); + + Bundle extras = getIntent().getExtras(); + this.roomId = extras.getString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), ""); + this.currentConversation = Parcels.unwrap(extras.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM())); + this.userBeingCalled = extras.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()); + + this.originalBundle = extras; + credentials = ApiUtils.getCredentials(userBeingCalled.getUsername(), userBeingCalled.getToken()); + + setCallDescriptionText(); + + if (currentConversation == null) { + handleFromNotification(); + } else { + setUpAfterConversationIsKnown(); + } + + if (DoNotDisturbUtils.INSTANCE.shouldPlaySound()) { + playRingtoneSound(); + } + + initClickListeners(); + } + + @Override + public void onStart() { + super.onStart(); + + if (handler == null) { + handler = new Handler(); + + try { + cache.evictAll(); + } catch (IOException e) { + Log.e(TAG, "Failed to evict cache"); + } + } + } + + private void initClickListeners() { + binding.callAnswerVoiceOnlyView.setOnClickListener(l -> { + Log.d(TAG, "accept call (voice only)"); + originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), true); + proceedToCall(); + }); + + binding.callAnswerCameraView.setOnClickListener(l -> { + Log.d(TAG, "accept call (with video)"); + originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false); + proceedToCall(); + }); + + binding.hangupButton.setOnClickListener(l -> hangup()); + } + + private void setCallDescriptionText() { + String callDescriptionWithoutTypeInfo = + String.format( + getResources().getString(R.string.nc_call_unknown), + getResources().getString(R.string.nc_app_product_name)); + + binding.incomingCallVoiceOrVideoTextView.setText(callDescriptionWithoutTypeInfo); + } + + private void showAnswerControls() { + binding.callAnswerCameraView.setVisibility(View.VISIBLE); + binding.callAnswerVoiceOnlyView.setVisibility(View.VISIBLE); + } + + @OnClick(R.id.hangupButton) + void hangup() { + leavingScreen = true; + finish(); + } + + private void proceedToCall() { + originalBundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), currentConversation.getToken()); + originalBundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), currentConversation.getDisplayName()); + + Intent intent = new Intent(this, CallActivity.class); + intent.putExtras(originalBundle); + startActivity(intent); + } + + private void checkIfAnyParticipantsRemainInRoom() { + int apiVersion = ApiUtils.getCallApiVersion(userBeingCalled, new int[]{ApiUtils.APIv4, 1}); + + ncApi.getPeersForCall(credentials, ApiUtils.getUrlForCall(apiVersion, userBeingCalled.getBaseUrl(), + currentConversation.getToken())) + .subscribeOn(Schedulers.io()) + .takeWhile(observable -> !leavingScreen) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + disposablesList.add(d); + } + + @Override + public void onNext(ParticipantsOverall participantsOverall) { + boolean hasParticipantsInCall = false; + boolean inCallOnDifferentDevice = false; + List participantList = participantsOverall.getOcs().getData(); + hasParticipantsInCall = participantList.size() > 0; + + if (hasParticipantsInCall) { + for (Participant participant : participantList) { + if (participant.getActorType() == Participant.ActorType.USERS && + participant.getActorId().equals(userBeingCalled.getUserId())) { + inCallOnDifferentDevice = true; + break; + } + } + } + + if (!hasParticipantsInCall || inCallOnDifferentDevice) { + runOnUiThread(() -> hangup()); + } + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + if (!leavingScreen) { + handler.postDelayed(() -> checkIfAnyParticipantsRemainInRoom(), 5000); + } + } + }); + + } + + private void handleFromNotification() { + int apiVersion = ApiUtils.getConversationApiVersion(userBeingCalled, new int[]{ApiUtils.APIv4, + ApiUtils.APIv3, 1}); + + ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, userBeingCalled.getBaseUrl(), roomId)) + .subscribeOn(Schedulers.io()) + .retry(3) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + disposablesList.add(d); + } + + @Override + public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) { + currentConversation = roomOverall.getOcs().data; + setUpAfterConversationIsKnown(); + + if (apiVersion >= 3) { + boolean hasCallFlags = + CapabilitiesUtil.hasSpreedFeatureCapability(userBeingCalled, + "conversation-call-flags"); + if (hasCallFlags) { + if (isInCallWithVideo(currentConversation.callFlag)) { + binding.incomingCallVoiceOrVideoTextView.setText( + String.format(getResources().getString(R.string.nc_call_video), + getResources().getString(R.string.nc_app_product_name))); + } else { + binding.incomingCallVoiceOrVideoTextView.setText( + String.format(getResources().getString(R.string.nc_call_voice), + getResources().getString(R.string.nc_app_product_name))); + } + } + } + } + + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + Log.e(TAG, e.getMessage(), 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 setUpAfterConversationIsKnown() { + binding.conversationNameTextView.setText(currentConversation.getDisplayName()); + + if(currentConversation.getType() == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL){ + setAvatarForOneToOneCall(); + } else { + binding.avatarImageView.setImageResource(R.drawable.ic_circular_group); + } + + checkIfAnyParticipantsRemainInRoom(); + showAnswerControls(); + } + + private void setAvatarForOneToOneCall() { + ImageRequest imageRequest = + DisplayUtils.getImageRequestForUrl( + ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(), + currentConversation.getName(), + R.dimen.avatar_size_big), null); + + ImagePipeline imagePipeline = Fresco.getImagePipeline(); + DataSource> dataSource = imagePipeline.fetchDecodedImage(imageRequest, null); + + dataSource.subscribe(new BaseBitmapDataSubscriber() { + @Override + protected void onNewResultImpl(@Nullable Bitmap bitmap) { + binding.avatarImageView.getHierarchy().setImage( + new BitmapDrawable(getResources(), bitmap), + 100, + true); + } + + @Override + protected void onFailureImpl(DataSource> dataSource) { + Log.e(TAG, "failed to load avatar"); + } + }, UiThreadImmediateExecutorService.getInstance()); + } + + private void endMediaNotifications() { + if (mediaPlayer != null) { + if (mediaPlayer.isPlaying()) { + mediaPlayer.stop(); + } + + mediaPlayer.release(); + mediaPlayer = null; + } + } + + @Override + public void onDestroy() { + leavingScreen = true; + if (handler != null) { + handler.removeCallbacksAndMessages(null); + handler = null; + } + dispose(); + endMediaNotifications(); + super.onDestroy(); + } + + private void dispose() { + if (disposablesList != null) { + for (Disposable disposable : disposablesList) { + if (!disposable.isDisposed()) { + disposable.dispose(); + } + } + } + } + + 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) { + mediaPlayer = new MediaPlayer(); + try { + mediaPlayer.setDataSource(this, 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"); + } + } + } + + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); + isInPipMode = isInPictureInPictureMode; + if (isInPictureInPictureMode) { + updateUiForPipMode(); + } else { + updateUiForNormalMode(); + } + } + + public void updateUiForPipMode() { + binding.callAnswerButtons.setVisibility(View.INVISIBLE); + binding.incomingCallRelativeLayout.setVisibility(View.INVISIBLE); + } + + public void updateUiForNormalMode() { + binding.callAnswerButtons.setVisibility(View.VISIBLE); + binding.incomingCallRelativeLayout.setVisibility(View.VISIBLE); + } + + @Override + void suppressFitsSystemWindows() { + binding.controllerCallNotificationLayout.setFitsSystemWindows(false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt deleted file mode 100644 index 496756524..000000000 --- a/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * @author Andy Scherzinger - * Copyright (C) 2021 Andy Scherzinger (infoi@andy-scherzinger.de) - * Copyright (C) 2017-2018 Mario Danic - * - * 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 . - */ - -package com.nextcloud.talk.activities - -import android.app.KeyguardManager -import android.content.res.Configuration -import android.os.Build -import android.os.Bundle -import android.view.View -import android.view.Window -import android.view.WindowManager -import autodagger.AutoInjector -import com.bluelinelabs.conductor.Conductor -import com.bluelinelabs.conductor.Router -import com.bluelinelabs.conductor.RouterTransaction -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.nextcloud.talk.R -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.controllers.CallController -import com.nextcloud.talk.controllers.CallNotificationController -import com.nextcloud.talk.controllers.ChatController -import com.nextcloud.talk.databinding.ActivityMagicCallBinding -import com.nextcloud.talk.events.ConfigurationChangeEvent -import com.nextcloud.talk.utils.bundle.BundleKeys - -@AutoInjector(NextcloudTalkApplication::class) -class MagicCallActivity : BaseActivity() { - lateinit var binding: ActivityMagicCallBinding - - private lateinit var chatController: ChatController - - private var router: Router? = null - private var chatRouter: Router? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - setTheme(R.style.CallTheme) - - requestWindowFeature(Window.FEATURE_NO_TITLE) - dismissKeyguard() - window.addFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN or - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - ) - window.decorView.systemUiVisibility = systemUiVisibility - - binding = ActivityMagicCallBinding.inflate(layoutInflater) - setContentView(binding.root) - - router = Conductor.attachRouter(this, binding.controllerContainer, savedInstanceState) - router!!.setPopsLastView(false) - - if (!router!!.hasRootController()) { - if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { - router!!.setRoot( - RouterTransaction.with(CallNotificationController(intent.extras)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler()) - ) - } else { - router!!.setRoot( - RouterTransaction.with(CallController(intent.extras)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler()) - ) - } - } - - val extras = intent.extras ?: Bundle() - extras.putBoolean("showToggleChat", true) - - chatController = ChatController(extras) - chatRouter = Conductor.attachRouter(this, binding.chatControllerView, savedInstanceState) - chatRouter!!.setRoot( - RouterTransaction.with(chatController) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler()) - ) - } - - fun showChat() { - enableKeyguard() - binding.chatControllerView.visibility = View.VISIBLE - binding.controllerContainer.visibility = View.GONE - chatController.wasDetached = false - chatController.pullChatMessages(1) - } - - fun showCall() { - binding.controllerContainer.visibility = View.VISIBLE - binding.chatControllerView.visibility = View.GONE - chatController.wasDetached = true - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - eventBus.post(ConfigurationChangeEvent()) - } - - private fun dismissKeyguard() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - setShowWhenLocked(true) - setTurnScreenOn(true) - val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager - keyguardManager.requestDismissKeyguard(this, null) - } else { - window.addFlags( - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - ) - } - } - - private fun enableKeyguard() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - setShowWhenLocked(false) - } else { - window.clearFlags( - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - ) - } - } - - companion object { - private val TAG = "MagicCallActivity" - - private val systemUiVisibility: Int - get() { - var flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN - flags = flags or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - return flags - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index 7e803a7a7..de8dbf73b 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -40,7 +40,6 @@ import com.google.android.material.snackbar.Snackbar import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.controllers.CallNotificationController import com.nextcloud.talk.controllers.ConversationsListController import com.nextcloud.talk.controllers.LockedController import com.nextcloud.talk.controllers.ServerSelectionController @@ -310,11 +309,9 @@ class MainActivity : BaseActivity(), ActionBarProvider { handleActionFromContact(intent) if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) { if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { - router!!.pushController( - RouterTransaction.with(CallNotificationController(intent.extras)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler()) - ) + val callNotificationIntent = Intent(this, CallNotificationActivity::class.java) + intent.extras?.let { callNotificationIntent.putExtras(it) } + startActivity(callNotificationIntent) } else { ConductorRemapping.remapChatController( router!!, intent.getLongExtra(BundleKeys.KEY_INTERNAL_USER_ID, -1), diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java index d0b4b43fc..9a383552a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java @@ -15,6 +15,7 @@ import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.interfaces.DraweeController; import com.facebook.drawee.view.SimpleDraweeView; import com.nextcloud.talk.R; +import com.nextcloud.talk.activities.CallActivity; import com.nextcloud.talk.utils.DisplayUtils; import org.webrtc.MediaStream; @@ -79,6 +80,8 @@ public class ParticipantsAdapter extends BaseAdapter { surfaceViewRenderer = convertView.findViewById(R.id.surface_view); try { + Log.d(TAG, "hasSurface: " + participantDisplayItem.getRootEglBase().hasSurface()); + surfaceViewRenderer.setMirror(false); surfaceViewRenderer.init(participantDisplayItem.getRootEglBase().getEglBaseContext(), null); surfaceViewRenderer.setZOrderMediaOverlay(false); @@ -96,7 +99,6 @@ public class ParticipantsAdapter extends BaseAdapter { layoutParams.height = scaleGridViewItemHeight(); convertView.setLayoutParams(layoutParams); - TextView nickTextView = convertView.findViewById(R.id.peer_nick_text_view); SimpleDraweeView imageView = convertView.findViewById(R.id.avatarImageView); @@ -110,8 +112,13 @@ public class ParticipantsAdapter extends BaseAdapter { } else { imageView.setVisibility(View.VISIBLE); surfaceViewRenderer.setVisibility(View.INVISIBLE); - nickTextView.setVisibility(View.VISIBLE); - nickTextView.setText(participantDisplayItem.getNick()); + + if (((CallActivity) mContext).isInPipMode) { + nickTextView.setVisibility(View.GONE); + } else { + nickTextView.setVisibility(View.VISIBLE); + nickTextView.setText(participantDisplayItem.getNick()); + } imageView.setController(null); DraweeController draweeController = Fresco.newDraweeControllerBuilder() @@ -127,7 +134,9 @@ public class ParticipantsAdapter extends BaseAdapter { } else { audioOffView.setVisibility(View.INVISIBLE); } + return convertView; + } private boolean hasVideoStream(ParticipantDisplayItem participantDisplayItem, MediaStream mediaStream) { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java deleted file mode 100644 index 66e6dcdf7..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * 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 . - */ - -package com.nextcloud.talk.controllers; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.media.AudioAttributes; -import android.media.MediaPlayer; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.renderscript.RenderScript; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.bluelinelabs.conductor.RouterTransaction; -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; -import com.bluelinelabs.logansquare.LoganSquare; -import com.facebook.common.executors.UiThreadImmediateExecutorService; -import com.facebook.common.references.CloseableReference; -import com.facebook.datasource.DataSource; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.view.SimpleDraweeView; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; -import com.facebook.imagepipeline.image.CloseableImage; -import com.facebook.imagepipeline.postprocessors.BlurPostProcessor; -import com.facebook.imagepipeline.request.ImageRequest; -import com.nextcloud.talk.R; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.controllers.base.BaseController; -import com.nextcloud.talk.events.CallNotificationClick; -import com.nextcloud.talk.events.ConfigurationChangeEvent; -import com.nextcloud.talk.models.RingtoneSettings; -import com.nextcloud.talk.models.database.CapabilitiesUtil; -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.participants.Participant; -import com.nextcloud.talk.models.json.participants.ParticipantsOverall; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.DisplayUtils; -import com.nextcloud.talk.utils.DoNotDisturbUtils; -import com.nextcloud.talk.utils.bundle.BundleKeys; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder; - -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.michaelevans.colorart.library.ColorArt; -import org.parceler.Parcels; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import autodagger.AutoInjector; -import butterknife.BindView; -import butterknife.OnClick; -import io.reactivex.Observer; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import okhttp3.Cache; - -@AutoInjector(NextcloudTalkApplication.class) -public class CallNotificationController extends BaseController { - - private static final String TAG = "CallNotificationController"; - - @Inject - NcApi ncApi; - - @Inject - AppPreferences appPreferences; - - @Inject - Cache cache; - - @Inject - EventBus eventBus; - - @Inject - Context context; - - @BindView(R.id.incomingCallVoiceOrVideoTextView) - TextView incomingCallVoiceOrVideoTextView; - - @BindView(R.id.conversationNameTextView) - TextView conversationNameTextView; - - @BindView(R.id.avatarImageView) - SimpleDraweeView avatarImageView; - - @BindView(R.id.callAnswerVoiceOnlyView) - SimpleDraweeView callAnswerVoiceOnlyView; - - @BindView(R.id.callAnswerCameraView) - SimpleDraweeView callAnswerCameraView; - - @BindView(R.id.backgroundImageView) - ImageView backgroundImageView; - - @BindView(R.id.incomingTextRelativeLayout) - RelativeLayout incomingTextRelativeLayout; - - private List disposablesList = new ArrayList<>(); - private Bundle originalBundle; - private String roomId; - private UserEntity userBeingCalled; - private String credentials; - private Conversation currentConversation; - private MediaPlayer mediaPlayer; - private boolean leavingScreen = false; - private RenderScript renderScript; - private Handler handler; - - public CallNotificationController(Bundle args) { - super(args); - NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); - - eventBus.post(new CallNotificationClick()); - this.roomId = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), ""); - this.currentConversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM())); - this.userBeingCalled = args.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()); - - this.originalBundle = args; - credentials = ApiUtils.getCredentials(userBeingCalled.getUsername(), userBeingCalled.getToken()); - } - - @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_call_notification, container, false); - } - - private void showAnswerControls() { - callAnswerCameraView.setVisibility(View.VISIBLE); - callAnswerVoiceOnlyView.setVisibility(View.VISIBLE); - } - - @OnClick(R.id.callControlHangupView) - void hangup() { - leavingScreen = true; - - if (getActivity() != null) { - getActivity().finish(); - } - } - - @OnClick(R.id.callAnswerCameraView) - void answerWithCamera() { - originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false); - proceedToCall(); - } - - @OnClick(R.id.callAnswerVoiceOnlyView) - void answerVoiceOnly() { - originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), true); - proceedToCall(); - } - - private void proceedToCall() { - originalBundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), currentConversation.getToken()); - originalBundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), currentConversation.getDisplayName()); - - getRouter().replaceTopController(RouterTransaction.with(new CallController(originalBundle)) - .popChangeHandler(new HorizontalChangeHandler()) - .pushChangeHandler(new HorizontalChangeHandler())); - } - - private void checkIfAnyParticipantsRemainInRoom() { - int apiVersion = ApiUtils.getCallApiVersion(userBeingCalled, new int[] {ApiUtils.APIv4, 1}); - - ncApi.getPeersForCall(credentials, ApiUtils.getUrlForCall(apiVersion, userBeingCalled.getBaseUrl(), - currentConversation.getToken())) - .subscribeOn(Schedulers.io()) - .takeWhile(observable -> !leavingScreen) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - disposablesList.add(d); - } - - @Override - public void onNext(ParticipantsOverall participantsOverall) { - boolean hasParticipantsInCall = false; - boolean inCallOnDifferentDevice = false; - List participantList = participantsOverall.getOcs().getData(); - hasParticipantsInCall = participantList.size() > 0; - - if (hasParticipantsInCall) { - for (Participant participant : participantList) { - if (participant.getActorType() == Participant.ActorType.USERS && - participant.getActorId().equals(userBeingCalled.getUserId())) { - inCallOnDifferentDevice = true; - break; - } - } - } - - if (!hasParticipantsInCall || inCallOnDifferentDevice) { - if (getActivity() != null) { - getActivity().runOnUiThread(() -> hangup()); - } - } - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - if (!leavingScreen) { - handler.postDelayed(() -> checkIfAnyParticipantsRemainInRoom(), 5000); - } - } - }); - - } - - private void handleFromNotification() { - int apiVersion = ApiUtils.getConversationApiVersion(userBeingCalled, new int[] {ApiUtils.APIv4, - ApiUtils.APIv3, 1}); - - ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, userBeingCalled.getBaseUrl(), roomId)) - .subscribeOn(Schedulers.io()) - .retry(3) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { - disposablesList.add(d); - } - - @Override - public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) { - currentConversation = roomOverall.getOcs().data; - runAllThings(); - - if (apiVersion >= 3) { - boolean hasCallFlags = - CapabilitiesUtil.hasSpreedFeatureCapability(userBeingCalled, - "conversation-call-flags"); - if (hasCallFlags) { - if (isInCallWithVideo(currentConversation.callFlag)) { - incomingCallVoiceOrVideoTextView.setText( - String.format(getResources().getString(R.string.nc_call_video), - getResources().getString(R.string.nc_app_product_name))); - } else { - incomingCallVoiceOrVideoTextView.setText( - String.format(getResources().getString(R.string.nc_call_voice), - getResources().getString(R.string.nc_app_product_name))); - } - } - } - } - - @SuppressLint("LongLogTag") - @Override - public void onError(@io.reactivex.annotations.NonNull Throwable e) { - Log.e(TAG, e.getMessage(), 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() { - if (conversationNameTextView != null) { - conversationNameTextView.setText(currentConversation.getDisplayName()); - } - - loadAvatar(); - checkIfAnyParticipantsRemainInRoom(); - showAnswerControls(); - } - - @SuppressLint({"LongLogTag"}) - @Override - protected void onViewBound(@NonNull View view) { - super.onViewBound(view); - - String callDescriptionWithoutTypeInfo = - String.format( - getResources().getString(R.string.nc_call_unknown), - getResources().getString(R.string.nc_app_product_name)); - - incomingCallVoiceOrVideoTextView.setText(callDescriptionWithoutTypeInfo); - - renderScript = RenderScript.create(getActivity()); - - if (handler == null) { - handler = new Handler(); - - try { - cache.evictAll(); - } catch (IOException e) { - Log.e(TAG, "Failed to evict cache"); - } - } - - if (currentConversation == null) { - handleFromNotification(); - } else { - runAllThings(); - } - - if (DoNotDisturbUtils.INSTANCE.shouldPlaySound()) { - playRingtoneSound(); - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onMessageEvent(ConfigurationChangeEvent configurationChangeEvent) { - ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) avatarImageView.getLayoutParams(); - int dimen = (int) getResources().getDimension(R.dimen.avatar_size_very_big); - - layoutParams.width = dimen; - layoutParams.height = dimen; - avatarImageView.setLayoutParams(layoutParams); - } - - @Override - protected void onDetach(@NonNull View view) { - super.onDetach(view); - eventBus.unregister(this); - } - - @Override - protected void onAttach(@NonNull View view) { - super.onAttach(view); - eventBus.register(this); - } - - private void loadAvatar() { - switch (currentConversation.getType()) { - case ROOM_TYPE_ONE_TO_ONE_CALL: - avatarImageView.setVisibility(View.VISIBLE); - - ImageRequest imageRequest = - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(), - currentConversation.getName(), - R.dimen.avatar_size_very_big), - null); - - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - DataSource> dataSource = imagePipeline.fetchDecodedImage(imageRequest, null); - - dataSource.subscribe(new BaseBitmapDataSubscriber() { - @Override - protected void onNewResultImpl(@Nullable Bitmap bitmap) { - if (avatarImageView != null) { - avatarImageView.getHierarchy().setImage(new BitmapDrawable(bitmap), 100, - true); - - if (getResources() != null) { - incomingTextRelativeLayout.setBackground( - getResources().getDrawable(R.drawable.incoming_gradient)); - } - - if (AvatarStatusCodeHolder.getInstance().getStatusCode() == 200 || - AvatarStatusCodeHolder.getInstance().getStatusCode() == 0) { - if (getActivity() != null) { - Bitmap backgroundBitmap = bitmap.copy(bitmap.getConfig(), true); - new BlurPostProcessor(5, getActivity()).process(backgroundBitmap); - backgroundImageView.setImageDrawable(new BitmapDrawable(backgroundBitmap)); - } - } else if (AvatarStatusCodeHolder.getInstance().getStatusCode() == 201) { - ColorArt colorArt = new ColorArt(bitmap); - int color = colorArt.getBackgroundColor(); - - float[] hsv = new float[3]; - Color.colorToHSV(color, hsv); - hsv[2] *= 0.75f; - color = Color.HSVToColor(hsv); - - backgroundImageView.setImageDrawable(new ColorDrawable(color)); - } - } - } - - @Override - protected void onFailureImpl(DataSource> dataSource) { - // unused atm - } - }, UiThreadImmediateExecutorService.getInstance()); - - break; - case ROOM_GROUP_CALL: - avatarImageView.setImageResource(R.drawable.ic_circular_group); - case ROOM_PUBLIC_CALL: - avatarImageView.setImageResource(R.drawable.ic_circular_group); - break; - default: - } - } - - private void endMediaNotifications() { - if (mediaPlayer != null) { - if (mediaPlayer.isPlaying()) { - mediaPlayer.stop(); - } - - mediaPlayer.release(); - mediaPlayer = null; - } - } - - @Override - public void onDestroy() { - AvatarStatusCodeHolder.getInstance().setStatusCode(0); - leavingScreen = true; - if (handler != null) { - handler.removeCallbacksAndMessages(null); - handler = null; - } - dispose(); - endMediaNotifications(); - super.onDestroy(); - } - - private void dispose() { - if (disposablesList != null) { - for (Disposable disposable : disposablesList) { - if (!disposable.isDisposed()) { - disposable.dispose(); - } - } - } - } - - @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"); - } - } - } -} \ 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 cd18dc807..703c21ee0 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -94,7 +94,7 @@ import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber import com.facebook.imagepipeline.image.CloseableImage import com.google.android.flexbox.FlexboxLayout import com.nextcloud.talk.R -import com.nextcloud.talk.activities.MagicCallActivity +import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder @@ -232,7 +232,6 @@ class ChatController(args: Bundle) : val roomId: String val voiceOnly: Boolean var isFirstMessagesProcessing = true - var isLeavingForConversation: Boolean = false var wasDetached: Boolean = false var emojiPopup: EmojiPopup? = null @@ -270,6 +269,8 @@ class ChatController(args: Bundle) : this.roomToken = args.getString(KEY_ROOM_TOKEN, "") this.sharedText = args.getString(BundleKeys.KEY_SHARED_TEXT, "") + Log.d(TAG, "roomToken = " + roomToken) + if (args.containsKey(KEY_ACTIVE_CONVERSATION)) { this.currentConversation = Parcels.unwrap(args.getParcelable(KEY_ACTIVE_CONVERSATION)) } @@ -290,6 +291,7 @@ class ChatController(args: Bundle) : } private fun getRoomInfo() { + Log.d(TAG, "getRoomInfo") val shouldRepeat = CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") if (shouldRepeat) { checkingLobbyStatus = true @@ -309,6 +311,8 @@ class ChatController(args: Bundle) : @Suppress("Detekt.TooGenericExceptionCaught") override fun onNext(roomOverall: RoomOverall) { currentConversation = roomOverall.ocs.data + Log.d(TAG, "currentConversation.toString : " + currentConversation.toString()) + Log.d(TAG, "currentConversation.sessionId : " + currentConversation?.sessionId) loadAvatarForStatusBar() setTitle() @@ -420,6 +424,7 @@ class ChatController(args: Bundle) : override fun onViewBound(view: View) { actionBar?.show() + Log.d(TAG, "onViewBound") var adapterWasNull = false if (adapter == null) { @@ -577,15 +582,6 @@ class ChatController(args: Bundle) : binding.messageInputView.setPadding(0, 0, 0, 0) - if (args.containsKey("showToggleChat") && args.getBoolean("showToggleChat")) { - binding.callControlToggleChat.visibility = View.VISIBLE - wasDetached = true - } - - binding.callControlToggleChat.setOnClickListener { - (activity as MagicCallActivity).showCall() - } - binding.messagesListView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) @@ -1376,10 +1372,8 @@ class ChatController(args: Bundle) : activity?.findViewById(R.id.toolbar)?.setOnClickListener { v -> showConversationInfoScreen() } } - isLeavingForConversation = false ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = roomId - ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = roomId - ApplicationWideCurrentRoomHolder.getInstance().isInCall = false + ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = roomToken ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser val smileyButton = binding.messageInputView.findViewById(R.id.smileyButton) @@ -1443,11 +1437,6 @@ class ChatController(args: Bundle) : override fun onDetach(view: View) { super.onDetach(view) - - if (!isLeavingForConversation) { - // current room is still "active", we need the info - ApplicationWideCurrentRoomHolder.getInstance().clear() - } eventBus?.unregister(this) if (activity != null) { @@ -1457,8 +1446,10 @@ class ChatController(args: Bundle) : if (conversationUser != null && activity != null && !activity?.isChangingConfigurations!! && - !isLeavingForConversation + !ApplicationWideCurrentRoomHolder.getInstance().isInCall && + !ApplicationWideCurrentRoomHolder.getInstance().isDialing ) { + ApplicationWideCurrentRoomHolder.getInstance().clear() wasDetached = true leaveRoom() } @@ -2129,7 +2120,7 @@ class ChatController(args: Bundle) : } private fun startACall(isVoiceOnlyCall: Boolean) { - isLeavingForConversation = true + ApplicationWideCurrentRoomHolder.getInstance().isDialing = true val callIntent = getIntentForCall(isVoiceOnlyCall) if (callIntent != null) { startActivity(callIntent) @@ -2151,7 +2142,7 @@ class ChatController(args: Bundle) : } return if (activity != null) { - val callIntent = Intent(activity, MagicCallActivity::class.java) + val callIntent = Intent(activity, CallActivity::class.java) callIntent.putExtras(bundle) callIntent } else { @@ -2336,7 +2327,7 @@ class ChatController(args: Bundle) : menu.findItem(R.id.action_forward_message).isVisible = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getMessageType() if (menu.hasVisibleItems()) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { setForceShowIcon(true) } show() @@ -2509,7 +2500,7 @@ class ChatController(args: Bundle) : } override fun onNext(roomOverall: RoomOverall) { - val conversationIntent = Intent(activity, MagicCallActivity::class.java) + val conversationIntent = Intent(activity, CallActivity::class.java) val bundle = Bundle() bundle.putParcelable(KEY_USER_ENTITY, conversationUser) bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs.data.token) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java index 59115a39e..3d202cfd4 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java @@ -22,7 +22,6 @@ package com.nextcloud.talk.controllers; import android.app.SearchManager; import android.content.Context; -import android.content.Intent; import android.graphics.PorterDuff; import android.os.Build; import android.os.Bundle; @@ -45,7 +44,6 @@ import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler; import com.bluelinelabs.logansquare.LoganSquare; import com.kennyc.bottomsheet.BottomSheet; import com.nextcloud.talk.R; -import com.nextcloud.talk.activities.MagicCallActivity; import com.nextcloud.talk.adapters.items.GenericTextHeaderItem; import com.nextcloud.talk.adapters.items.UserItem; import com.nextcloud.talk.api.NcApi; @@ -106,7 +104,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.SelectableAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; -import eu.davidea.flexibleadapter.items.IFlexible; import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -897,13 +894,10 @@ public class ContactsController extends BaseController implements SearchView.OnQ @Override public void onNext(RoomOverall roomOverall) { if (getActivity() != null) { - Intent conversationIntent = new Intent(getActivity(), MagicCallActivity.class); Bundle bundle = new Bundle(); bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser); bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomOverall.getOcs().getData().getToken()); bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), roomOverall.getOcs().getData().getRoomId()); - conversationIntent.putExtras(bundle); - bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(roomOverall.getOcs().getData())); diff --git a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java index d1226ba3a..d084ee654 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java @@ -38,7 +38,6 @@ import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; import com.bluelinelabs.logansquare.LoganSquare; import com.nextcloud.talk.R; -import com.nextcloud.talk.activities.MagicCallActivity; import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.controllers.base.BaseController; @@ -714,7 +713,6 @@ public class OperationsMenuController extends BaseController { eventBus.post(new BottomSheetLockEvent(true, 0, true, true, dismissView)); - Intent conversationIntent = new Intent(getActivity(), MagicCallActivity.class); Bundle bundle = new Bundle(); bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken()); bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), conversation.getRoomId()); @@ -723,8 +721,6 @@ public class OperationsMenuController extends BaseController { bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(conversation)); bundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), callPassword); - conversationIntent.putExtras(bundle); - if (getParentController() != null) { ConductorRemapping.INSTANCE.remapChatController(getParentController().getRouter(), currentUser.getId(), conversation.getToken(), bundle, true); diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java index 49dc31ec9..467ff1876 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java @@ -33,7 +33,6 @@ import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.LoggingUtils; import com.nextcloud.talk.utils.database.user.UserUtils; import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder; import com.nextcloud.talk.utils.ssl.MagicKeyManager; import com.nextcloud.talk.utils.ssl.MagicTrustManager; import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat; @@ -253,10 +252,6 @@ public class RestModule { Response response = chain.proceed(request); - if (request.url().encodedPath().contains("/avatar/")) { - AvatarStatusCodeHolder.getInstance().setStatusCode(response.code()); - } - return response; } } diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index d11134a45..9f2a2e09e 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -48,7 +48,7 @@ import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor; import com.facebook.imagepipeline.request.ImageRequest; import com.nextcloud.talk.R; -import com.nextcloud.talk.activities.MagicCallActivity; +import com.nextcloud.talk.activities.CallActivity; import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; @@ -590,7 +590,7 @@ public class NotificationWorker extends Worker { boolean startACall = decryptedPushMessage.getType().equals("call"); if (startACall) { - intent = new Intent(context, MagicCallActivity.class); + intent = new Intent(context, CallActivity.class); } else { intent = new Intent(context, MainActivity.class); } diff --git a/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java b/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java index 6ce60d386..1ab12a531 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java +++ b/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java @@ -28,6 +28,7 @@ public class ApplicationWideCurrentRoomHolder { private String currentRoomToken = ""; private UserEntity userInRoom = new UserEntity(); private boolean inCall = false; + private boolean isDialing = false; private String session = ""; public static ApplicationWideCurrentRoomHolder getInstance() { @@ -38,6 +39,7 @@ public class ApplicationWideCurrentRoomHolder { currentRoomId = ""; userInRoom = new UserEntity(); inCall = false; + isDialing = false; currentRoomToken = ""; session = ""; } @@ -74,6 +76,14 @@ public class ApplicationWideCurrentRoomHolder { this.inCall = inCall; } + public boolean isDialing() { + return isDialing; + } + + public void setDialing(boolean dialing) { + isDialing = dialing; + } + public String getSession() { return session; } diff --git a/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml b/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml new file mode 100644 index 000000000..7837510dd --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_magic_call.xml b/app/src/main/res/layout/activity_magic_call.xml deleted file mode 100644 index 7ea065fa5..000000000 --- a/app/src/main/res/layout/activity_magic_call.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/controller_call.xml b/app/src/main/res/layout/call_activity.xml similarity index 79% rename from app/src/main/res/layout/controller_call.xml rename to app/src/main/res/layout/call_activity.xml index a02000cfc..42d739733 100644 --- a/app/src/main/res/layout/controller_call.xml +++ b/app/src/main/res/layout/call_activity.xml @@ -28,7 +28,7 @@ android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical" - tools:context=".activities.MagicCallActivity"> + tools:context=".activities.CallActivity"> + + + + + + + + diff --git a/app/src/main/res/layout/controller_call_notification.xml b/app/src/main/res/layout/call_notification_activity.xml similarity index 87% rename from app/src/main/res/layout/controller_call_notification.xml rename to app/src/main/res/layout/call_notification_activity.xml index 76b47fa45..76c3f710c 100644 --- a/app/src/main/res/layout/controller_call_notification.xml +++ b/app/src/main/res/layout/call_notification_activity.xml @@ -22,19 +22,13 @@ - - + android:layout_height="match_parent" + android:background="@color/grey950"> @@ -119,10 +113,9 @@ + app:roundAsCircle="true" /> diff --git a/app/src/main/res/layout/call_states.xml b/app/src/main/res/layout/call_states.xml index 136805f57..1e2a354d6 100644 --- a/app/src/main/res/layout/call_states.xml +++ b/app/src/main/res/layout/call_states.xml @@ -21,7 +21,7 @@ --> diff --git a/app/src/main/res/layout/controller_chat.xml b/app/src/main/res/layout/controller_chat.xml index b65baa4a8..413e56ffb 100644 --- a/app/src/main/res/layout/controller_chat.xml +++ b/app/src/main/res/layout/controller_chat.xml @@ -40,19 +40,6 @@ android:visibility="gone" tools:visibility="visible" /> - - 24dp - - 120dp diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 61a76806e..e52c554ed 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -33,8 +33,6 @@ 40dp 30dp 96dp - @dimen/avatar_fetching_size_very_big - 180dp 14sp 6dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 501acb52c..ff1ce3b6b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -221,6 +221,10 @@ %1$s with phone %1$s with video + + Mute microphone + Enable microphone + %1$s on %2$s notification channel Calls notification channel @@ -468,4 +472,5 @@ %1$s (%2$d) Invalid password Do you want to reauthorize or delete this account? + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c266cc46d..ebd6e739b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -51,10 +51,6 @@ @color/fontAppbar - - @@ -187,6 +183,13 @@ @color/colorPrimary + + +