Improved PiP

This commit is contained in:
Sylvain Berfini 2024-04-16 11:48:34 +02:00
parent 8c7c4bee0d
commit 61fe57628f
8 changed files with 97 additions and 26 deletions

View file

@ -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 -> {

View file

@ -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)

View file

@ -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)) {

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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}" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/conference_layout_nav_host_fragment"

View file

@ -79,6 +79,7 @@
android:text="@{viewModel.displayedName, default=`John Doe`}"
android:textSize="22sp"
android:textColor="@color/white"
android:visibility="@{viewModel.pipMode ? View.GONE : View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/avatar"
@ -93,6 +94,7 @@
android:text="@{viewModel.displayedAddress, default=`sip:johndoe@sip.linphone.org`}"
android:textColor="@color/white"
android:textSize="14sp"
android:visibility="@{viewModel.pipMode ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@id/display_name"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -267,8 +269,8 @@
app:displayMode="black_bars"
app:layout_constraintBottom_toBottomOf="@id/hinge_bottom"
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}" />
<ImageView
android:id="@+id/recording"

View file

@ -62,6 +62,9 @@
<dimen name="call_button_icon_padding">15dp</dimen>
<dimen name="call_extra_button_top_margin">30dp</dimen>
<dimen name="call_video_preview_max_size">200dp</dimen>
<dimen name="call_video_preview_pip_max_size">45dp</dimen>
<dimen name="call_conference_audio_only_miniature_height">100dp</dimen>
<dimen name="call_conference_active_speaker_miniature_size">120dp</dimen>