mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Improved PiP
This commit is contained in:
parent
8c7c4bee0d
commit
61fe57628f
8 changed files with 97 additions and 26 deletions
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue