diff --git a/app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt
index 8175b495b..5fd108926 100644
--- a/app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt
+++ b/app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt
@@ -19,11 +19,14 @@
*/
package org.linphone.compatibility
+import android.app.Activity
import android.app.Notification
+import android.app.PictureInPictureParams
import android.app.Service
import android.net.Uri
import android.provider.MediaStore
import org.linphone.core.tools.Log
+import org.linphone.utils.AppUtils
class Api28Compatibility {
companion object {
@@ -40,6 +43,23 @@ class Api28Compatibility {
}
}
+ fun enterPipMode(activity: Activity): Boolean {
+ val params = PictureInPictureParams.Builder()
+ .setAspectRatio(AppUtils.getPipRatio(activity))
+ .build()
+ try {
+ if (!activity.enterPictureInPictureMode(params)) {
+ Log.e("$TAG Failed to enter PiP mode")
+ } else {
+ Log.i("$TAG Entered PiP mode")
+ return true
+ }
+ } catch (e: Exception) {
+ Log.e("$TAG Can't build PiP params: $e")
+ }
+ return false
+ }
+
fun getMediaCollectionUri(isImage: Boolean, isVideo: Boolean, isAudio: Boolean): Uri {
return when {
isImage -> {
diff --git a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt
index aa0679abf..c4b93f314 100644
--- a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt
+++ b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt
@@ -19,6 +19,8 @@
*/
package org.linphone.compatibility
+import android.app.Activity
+import android.app.PictureInPictureParams
import android.app.UiModeManager
import android.content.Context
import android.graphics.RenderEffect
@@ -28,12 +30,22 @@ import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import org.linphone.core.tools.Log
+import org.linphone.utils.AppUtils
@RequiresApi(Build.VERSION_CODES.S)
class Api31Compatibility {
companion object {
private const val TAG = "[API 31 Compatibility]"
+ fun enableAutoEnterPiP(activity: Activity, enable: Boolean) {
+ activity.setPictureInPictureParams(
+ PictureInPictureParams.Builder()
+ .setAspectRatio(AppUtils.getPipRatio(activity))
+ .setAutoEnterEnabled(enable)
+ .build()
+ )
+ }
+
fun setBlurRenderEffect(view: View) {
val blurEffect = RenderEffect.createBlurEffect(16F, 16F, Shader.TileMode.MIRROR)
view.setRenderEffect(blurEffect)
diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt
index 385c0ee56..51e6e19a9 100644
--- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt
+++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt
@@ -21,6 +21,7 @@ package org.linphone.compatibility
import android.Manifest
import android.annotation.SuppressLint
+import android.app.Activity
import android.app.Notification
import android.app.Service
import android.content.Context
@@ -108,6 +109,19 @@ class Compatibility {
return false
}
+ fun enterPipMode(activity: Activity): Boolean {
+ if (Version.sdkStrictlyBelow(Version.API31_ANDROID_12)) {
+ return Api28Compatibility.enterPipMode(activity)
+ }
+ return activity.isInPictureInPictureMode
+ }
+
+ fun enableAutoEnterPiP(activity: Activity, enable: Boolean) {
+ if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) {
+ Api31Compatibility.enableAutoEnterPiP(activity, enable)
+ }
+ }
+
fun forceDarkMode(context: Context) {
Log.i("$TAG Forcing dark/night theme")
if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) {
diff --git a/app/src/main/java/org/linphone/ui/call/CallActivity.kt b/app/src/main/java/org/linphone/ui/call/CallActivity.kt
index 2b8d833b4..24d786b9b 100644
--- a/app/src/main/java/org/linphone/ui/call/CallActivity.kt
+++ b/app/src/main/java/org/linphone/ui/call/CallActivity.kt
@@ -20,7 +20,6 @@
package org.linphone.ui.call
import android.Manifest
-import android.app.PictureInPictureParams
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
@@ -44,6 +43,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
+import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log
import org.linphone.databinding.CallActivityBinding
import org.linphone.ui.GenericActivity
@@ -57,7 +57,6 @@ import org.linphone.ui.call.model.AudioDeviceModel
import org.linphone.ui.call.viewmodel.CallsViewModel
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
import org.linphone.ui.call.viewmodel.SharedCallViewModel
-import org.linphone.utils.AppUtils
import org.linphone.utils.ToastUtils
import org.linphone.utils.slideInToastFromTop
import org.linphone.utils.slideInToastFromTopForDuration
@@ -76,6 +75,8 @@ class CallActivity : GenericActivity() {
private var bottomSheetDialog: BottomSheetDialogFragment? = null
+ private var isPipSupported = false
+
private val requestCameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
@@ -113,6 +114,11 @@ class CallActivity : GenericActivity() {
}
}
+ isPipSupported = packageManager.hasSystemFeature(
+ PackageManager.FEATURE_PICTURE_IN_PICTURE
+ )
+ Log.i("$TAG Is PiP supported [$isPipSupported]")
+
sharedViewModel = run {
ViewModelProvider(this)[SharedCallViewModel::class.java]
}
@@ -139,6 +145,13 @@ class CallActivity : GenericActivity() {
}
}
+ callViewModel.isVideoEnabled.observe(this) { enabled ->
+ if (isPipSupported) {
+ // Only enable PiP if video is enabled
+ Compatibility.enableAutoEnterPiP(this, enabled)
+ }
+ }
+
callViewModel.transferInProgressEvent.observe(this) {
it.consume {
showGreenToast(
@@ -249,8 +262,8 @@ class CallActivity : GenericActivity() {
super.onResume()
val isInPipMode = isInPictureInPictureMode
+ Log.i("$TAG onResume: is in PiP mode? [$isInPipMode]")
if (::callViewModel.isInitialized) {
- Log.i("$TAG onResume: is in PiP mode? $isInPipMode")
callViewModel.pipMode.value = isInPipMode
}
}
@@ -287,25 +300,13 @@ class CallActivity : GenericActivity() {
override fun onUserLeaveHint() {
super.onUserLeaveHint()
- if (::callViewModel.isInitialized && callViewModel.isVideoEnabled.value == true) {
- Log.i("$TAG User leave hint, entering PiP mode")
- val supportsPip = packageManager.hasSystemFeature(
- PackageManager.FEATURE_PICTURE_IN_PICTURE
- )
- Log.i("$TAG Is PiP supported: $supportsPip")
- if (supportsPip) {
- val params = PictureInPictureParams.Builder()
- .setAspectRatio(AppUtils.getPipRatio(this))
- .build()
- try {
- if (!enterPictureInPictureMode(params)) {
- Log.e("$TAG Failed to enter PiP mode")
- callViewModel.pipMode.value = false
- } else {
- Log.i("$TAG Entered PiP mode")
- }
- } catch (e: Exception) {
- Log.e("$TAG Can't build PiP params: $e")
+ if (::callViewModel.isInitialized) {
+ if (isPipSupported && callViewModel.isVideoEnabled.value == true) {
+ Log.i("$TAG User leave hint, entering PiP mode")
+ val pipMode = Compatibility.enterPipMode(this)
+ if (!pipMode) {
+ Log.e("$TAG Failed to enter PiP mode")
+ callViewModel.pipMode.value = false
}
}
}
diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
index 5da4b134d..3b3a261bf 100644
--- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
+++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
@@ -40,6 +40,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatEditText
import androidx.appcompat.widget.AppCompatTextView
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.doOnLayout
@@ -495,6 +496,24 @@ fun setConstraintLayoutChildHorizontalBias(view: View, horizontalBias: Float) {
view.layoutParams = params
}
+@BindingAdapter("layout_constraintHeight_max")
+fun setConstraintLayoutHeightMax(view: View, dp: Float) {
+ val layout = view.parent as ConstraintLayout
+ val cs = ConstraintSet()
+ cs.clone(layout)
+ cs.constrainMaxHeight(view.id, dp.toInt())
+ cs.applyTo(layout)
+}
+
+@BindingAdapter("layout_constraintWidth_max")
+fun setConstraintLayoutWidthMax(view: View, dp: Float) {
+ val layout = view.parent as ConstraintLayout
+ val cs = ConstraintSet()
+ cs.clone(layout)
+ cs.constrainMaxWidth(view.id, dp.toInt())
+ cs.applyTo(layout)
+}
+
@BindingAdapter("focusNextOnInput")
fun focusNextOnInput(editText: EditText, enabled: Boolean) {
if (!enabled) return
diff --git a/app/src/main/res/layout/call_active_conference_fragment.xml b/app/src/main/res/layout/call_active_conference_fragment.xml
index 0decc5c9f..d8a3e9eed 100644
--- a/app/src/main/res/layout/call_active_conference_fragment.xml
+++ b/app/src/main/res/layout/call_active_conference_fragment.xml
@@ -246,8 +246,8 @@
app:displayMode="black_bars"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHeight_max="200dp"
- app:layout_constraintWidth_max="200dp" />
+ app:layout_constraintHeight_max="@{viewModel.pipMode ? @dimen/call_video_preview_pip_max_size : @dimen/call_video_preview_max_size, default=@dimen/call_video_preview_max_size}"
+ app:layout_constraintWidth_max="@{viewModel.pipMode ? @dimen/call_video_preview_pip_max_size : @dimen/call_video_preview_max_size, default=@dimen/call_video_preview_max_size}" />
+ app:layout_constraintHeight_max="@{viewModel.pipMode ? @dimen/call_video_preview_pip_max_size : @dimen/call_video_preview_max_size, default=@dimen/call_video_preview_max_size}"
+ app:layout_constraintWidth_max="@{viewModel.pipMode ? @dimen/call_video_preview_pip_max_size : @dimen/call_video_preview_max_size, default=@dimen/call_video_preview_max_size}" />
15dp
30dp
+ 200dp
+ 45dp
+
100dp
120dp