diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt
index 85dae6730..e6ca53974 100644
--- a/app/src/main/java/org/linphone/core/CoreContext.kt
+++ b/app/src/main/java/org/linphone/core/CoreContext.kt
@@ -177,6 +177,30 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
Log.i("[Context] Starting call $call")
}
+ fun switchCamera() {
+ val currentDevice = core.videoDevice
+ Log.i("[Context] Current camera device is $currentDevice")
+
+ for (camera in core.videoDevicesList) {
+ if (camera != currentDevice && camera != "StaticImage: Static picture") {
+ Log.i("[Context] New camera device will be $camera")
+ core.videoDevice = camera
+ break
+ }
+ }
+
+ val call = core.currentCall
+ if (call == null) {
+ Log.w("[Context] Switching camera while not in call")
+ return
+ }
+ call.update(null)
+ }
+
+ fun showSwitchCameraButton(): Boolean {
+ return core.videoDevicesList.size > 2 // Count StaticImage camera
+ }
+
private fun showCallActivity() {
Log.i("[Context] Starting VoIP activity")
val intent = Intent(context, VoipActivity::class.java)
diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt
index 31c4e3cbc..834fb4093 100644
--- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt
+++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt
@@ -34,6 +34,8 @@ import org.linphone.databinding.MainActivityBinding
class MainActivity : AppCompatActivity() {
companion object {
private const val CONTACTS_PERMISSION_REQUEST = 0
+ private const val CAMERA_PERMISSION_REQUEST = 1
+ private const val RECORD_AUDIO_PERMISSION_REQUEST = 2
}
private lateinit var binding: MainActivityBinding
@@ -54,6 +56,8 @@ class MainActivity : AppCompatActivity() {
if (checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
loadContacts()
}
+ checkSelfPermission(Manifest.permission.CAMERA)
+ checkSelfPermission(Manifest.permission.RECORD_AUDIO)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
binding.lifecycleOwner = this
@@ -68,6 +72,18 @@ class MainActivity : AppCompatActivity() {
CONTACTS_PERMISSION_REQUEST
)
}
+ if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ arrayOf(Manifest.permission.CAMERA),
+ CAMERA_PERMISSION_REQUEST
+ )
+ }
+ if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ arrayOf(Manifest.permission.RECORD_AUDIO),
+ RECORD_AUDIO_PERMISSION_REQUEST
+ )
+ }
}
override fun onRequestPermissionsResult(
diff --git a/app/src/main/java/org/linphone/ui/voip/fragment/ActiveCallFragment.kt b/app/src/main/java/org/linphone/ui/voip/fragment/ActiveCallFragment.kt
index 30f6ebac0..46b765b98 100644
--- a/app/src/main/java/org/linphone/ui/voip/fragment/ActiveCallFragment.kt
+++ b/app/src/main/java/org/linphone/ui/voip/fragment/ActiveCallFragment.kt
@@ -22,9 +22,11 @@ package org.linphone.ui.voip.fragment
import android.os.Bundle
import android.os.SystemClock
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
+import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.databinding.VoipActiveCallFragmentBinding
import org.linphone.ui.main.fragment.GenericFragment
@@ -38,6 +40,33 @@ class ActiveCallFragment : GenericFragment() {
private lateinit var callViewModel: CurrentCallViewModel
+ // For moving video preview purposes
+
+ private var previewX: Float = 0f
+ private var previewY: Float = 0f
+
+ private val previewTouchListener = View.OnTouchListener { view, event ->
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ previewX = view.x - event.rawX
+ previewY = view.y - event.rawY
+ true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ view.animate()
+ .x(event.rawX + previewX)
+ .y(event.rawY + previewY)
+ .setDuration(0)
+ .start()
+ true
+ }
+ else -> {
+ view.performClick()
+ false
+ }
+ }
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -101,4 +130,19 @@ class ActiveCallFragment : GenericFragment() {
binding.chronometer.start()
}
}
+
+ override fun onResume() {
+ super.onResume()
+
+ coreContext.postOnCoreThread { core ->
+ core.nativeVideoWindowId = binding.remoteVideoSurface
+ coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface
+ binding.localPreviewVideoSurface.setOnTouchListener(previewTouchListener)
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ binding.localPreviewVideoSurface.setOnTouchListener(null)
+ }
}
diff --git a/app/src/main/java/org/linphone/ui/voip/view/RoundCornersTextureView.kt b/app/src/main/java/org/linphone/ui/voip/view/RoundCornersTextureView.kt
new file mode 100644
index 000000000..728656604
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/voip/view/RoundCornersTextureView.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2010-2021 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 org.linphone.ui.voip.view
+
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewOutlineProvider
+import org.linphone.R
+import org.linphone.mediastream.video.capture.CaptureTextureView
+
+class RoundCornersTextureView : CaptureTextureView {
+ private var mRadius: Float = 0f
+
+ constructor(context: Context) : super(context) {
+ mAlignTopRight = true
+ mDisplayMode = DisplayMode.BLACK_BARS
+ setRoundCorners()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ readAttributes(attrs)
+ setRoundCorners()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
+ context,
+ attrs,
+ defStyleAttr
+ ) {
+ readAttributes(attrs)
+ setRoundCorners()
+ }
+
+ private fun readAttributes(attrs: AttributeSet) {
+ context.theme.obtainStyledAttributes(
+ attrs,
+ R.styleable.RoundCornersTextureView,
+ 0,
+ 0
+ ).apply {
+ try {
+ mAlignTopRight = getBoolean(R.styleable.RoundCornersTextureView_alignTopRight, true)
+ val mode = getInteger(
+ R.styleable.RoundCornersTextureView_displayMode,
+ DisplayMode.BLACK_BARS.ordinal
+ )
+ mDisplayMode = when (mode) {
+ 1 -> DisplayMode.OCCUPY_ALL_SPACE
+ 2 -> DisplayMode.HYBRID
+ else -> DisplayMode.BLACK_BARS
+ }
+ mRadius = getFloat(
+ R.styleable.RoundCornersTextureView_radius,
+ context.resources.getDimension(
+ R.dimen.in_call_round_corners_texture_view_radius
+ )
+ )
+ } finally {
+ recycle()
+ }
+ }
+ }
+
+ private fun setRoundCorners() {
+ outlineProvider = object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ val rect = if (previewRectF != null &&
+ actualDisplayMode == DisplayMode.BLACK_BARS &&
+ mAlignTopRight
+ ) {
+ Rect(
+ previewRectF.left.toInt(),
+ previewRectF.top.toInt(),
+ previewRectF.right.toInt(),
+ previewRectF.bottom.toInt()
+ )
+ } else {
+ Rect(
+ 0,
+ 0,
+ width,
+ height
+ )
+ }
+ outline.setRoundRect(rect, mRadius)
+ }
+ }
+ clipToOutline = true
+ }
+
+ override fun setAspectRatio(width: Int, height: Int) {
+ super.setAspectRatio(width, height)
+
+ val previewSize = previewVideoSize
+ if (previewSize.width > 0 && previewSize.height > 0) {
+ setRoundCorners()
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/voip/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/voip/viewmodel/CurrentCallViewModel.kt
index 83110755d..4e637efbe 100644
--- a/app/src/main/java/org/linphone/ui/voip/viewmodel/CurrentCallViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/voip/viewmodel/CurrentCallViewModel.kt
@@ -27,6 +27,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.Call
import org.linphone.core.CallListenerStub
+import org.linphone.core.MediaDirection
import org.linphone.core.MediaEncryption
import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactAvatarModel
@@ -50,14 +51,19 @@ class CurrentCallViewModel() : ViewModel() {
val isMicrophoneMuted = MutableLiveData()
+ val fullScreenMode = MutableLiveData()
+
+ // To synchronize chronometers in UI
+ val callDuration = MutableLiveData()
+
+ // ZRTP related
+
val isRemoteDeviceTrusted = MutableLiveData()
val showZrtpSasDialogEvent: MutableLiveData>> by lazy {
MutableLiveData>>()
}
- val callDuration = MutableLiveData()
-
// Extras actions
val isActionsMenuExpanded = MutableLiveData()
@@ -88,11 +94,20 @@ class CurrentCallViewModel() : ViewModel() {
override fun onEncryptionChanged(call: Call, on: Boolean, authenticationToken: String?) {
updateEncryption()
}
+
+ override fun onStateChanged(call: Call, state: Call.State?, message: String) {
+ if (LinphoneUtils.isCallOutgoing(call.state)) {
+ isVideoEnabled.postValue(call.params.isVideoEnabled)
+ } else {
+ isVideoEnabled.postValue(call.currentParams.isVideoEnabled)
+ }
+ }
}
init {
isVideoEnabled.value = false
isMicrophoneMuted.value = false
+ fullScreenMode.value = false
isActionsMenuExpanded.value = false
extraActionsMenuTranslateY.value = extraActionsMenuHeight
@@ -119,6 +134,14 @@ class CurrentCallViewModel() : ViewModel() {
}
}
+ fun answer() {
+ // UI thread
+ coreContext.postOnCoreThread {
+ Log.i("$TAG Answering call [$call]")
+ call.accept()
+ }
+ }
+
fun hangUp() {
// UI thread
coreContext.postOnCoreThread {
@@ -154,7 +177,38 @@ class CurrentCallViewModel() : ViewModel() {
// UI thread
// TODO: check video permission
- // TODO
+ coreContext.postOnCoreThread { core ->
+ if (::call.isInitialized) {
+ val params = core.createCallParams(call)
+ if (call.conference != null) {
+ if (params?.isVideoEnabled == false) {
+ params.isVideoEnabled = true
+ params.videoDirection = MediaDirection.SendRecv
+ } else {
+ if (params?.videoDirection == MediaDirection.SendRecv || params?.videoDirection == MediaDirection.SendOnly) {
+ params.videoDirection = MediaDirection.RecvOnly
+ } else {
+ params?.videoDirection = MediaDirection.SendRecv
+ }
+ }
+ } else {
+ params?.isVideoEnabled = params?.isVideoEnabled == false
+ Log.i(
+ "$TAG Updating call with video enabled set to ${params?.isVideoEnabled}"
+ )
+ }
+ call.update(params)
+ }
+ }
+ }
+
+ fun switchCamera() {
+ coreContext.switchCamera()
+ }
+
+ fun toggleFullScreen() {
+ if (fullScreenMode.value == false && isVideoEnabled.value == false) return
+ fullScreenMode.value = fullScreenMode.value != true
}
fun toggleExpandActionsMenu() {
diff --git a/app/src/main/res/drawable/shape_answer_button_background.xml b/app/src/main/res/drawable/shape_answer_button_background.xml
new file mode 100644
index 000000000..3b8ff1a6a
--- /dev/null
+++ b/app/src/main/res/drawable/shape_answer_button_background.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/voip_active_call_fragment.xml b/app/src/main/res/layout/voip_active_call_fragment.xml
index c34a834e6..9bc263c66 100644
--- a/app/src/main/res/layout/voip_active_call_fragment.xml
+++ b/app/src/main/res/layout/voip_active_call_fragment.xml
@@ -65,13 +65,14 @@
app:layout_constraintBottom_toBottomOf="@id/call_direction_label"/>
+
+
+
+
@@ -74,6 +75,7 @@
app:layout_constraintEnd_toStartOf="@id/change_audio_output" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/voip_outgoing_call_fragment.xml b/app/src/main/res/layout/voip_outgoing_call_fragment.xml
index 960bb7658..ac999da73 100644
--- a/app/src/main/res/layout/voip_outgoing_call_fragment.xml
+++ b/app/src/main/res/layout/voip_outgoing_call_fragment.xml
@@ -40,13 +40,14 @@
app:layout_constraintTop_toTopOf="parent"/>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml
index 5d74d6bcd..a76a5d803 100644
--- a/app/src/main/res/values/dimen.xml
+++ b/app/src/main/res/values/dimen.xml
@@ -14,6 +14,7 @@
116dp
237dp
353dp
+ 20dp
360dp
\ No newline at end of file