From 9d6b561a2d6d09915a9d2c764949cb0b4e7c9247 Mon Sep 17 00:00:00 2001 From: Francesco Romano Date: Wed, 2 Jul 2025 16:17:47 +0200 Subject: [PATCH 1/2] Update video player logic: now it pauses also when another window is occluding it (in multi-window mode) --- .../developers/androidify/MainActivity.kt | 25 ++++++++++++++++++- .../androidify/navigation/MainNavigation.kt | 5 ++-- .../developers/androidify/home/HomeScreen.kt | 18 +++++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/android/developers/androidify/MainActivity.kt b/app/src/main/java/com/android/developers/androidify/MainActivity.kt index 7b005cf5..fefe6d67 100644 --- a/app/src/main/java/com/android/developers/androidify/MainActivity.kt +++ b/app/src/main/java/com/android/developers/androidify/MainActivity.kt @@ -15,12 +15,16 @@ */ package com.android.developers.androidify +import android.os.Build import android.os.Bundle +import android.view.WindowManager +import android.window.TrustedPresentationThresholds import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.android.developers.androidify.navigation.MainNavigation @@ -31,6 +35,8 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { + private val isWindowNotOccluded = mutableStateOf(true) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -46,8 +52,25 @@ class MainActivity : ComponentActivity() { Color.Transparent.toArgb(), ), ) - MainNavigation() + MainNavigation(isWindowNotOccluded) } } } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + val presentationThreshold = TrustedPresentationThresholds( + 1f, 0.25f, 500 + ) + + val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager + windowManager.registerTrustedPresentationListener( + window.decorView.windowToken, + presentationThreshold, + mainExecutor + ) { notOccluded -> isWindowNotOccluded.value = notOccluded } + } + } + } diff --git a/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt b/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt index 7327d87d..8e82054a 100644 --- a/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt +++ b/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt @@ -26,6 +26,7 @@ import androidx.compose.animation.scaleOut import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -42,12 +43,11 @@ import com.android.developers.androidify.creation.CreationScreen import com.android.developers.androidify.home.AboutScreen import com.android.developers.androidify.home.HomeScreen import com.android.developers.androidify.theme.transitions.ColorSplashTransitionScreen -import com.google.android.gms.oss.licenses.OssLicensesActivity import com.google.android.gms.oss.licenses.OssLicensesMenuActivity @ExperimentalMaterial3ExpressiveApi @Composable -fun MainNavigation() { +fun MainNavigation(isWindowNotOccluded: MutableState) { val backStack = rememberMutableStateListOf(Home) var positionReveal by remember { mutableStateOf(IntOffset.Zero) @@ -80,6 +80,7 @@ fun MainNavigation() { entryProvider = entryProvider { entry { entry -> HomeScreen( + isWindowNotOccluded = isWindowNotOccluded, onClickLetsGo = { positionOffset -> showSplash = true positionReveal = positionOffset diff --git a/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt b/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt index 3de5c427..2cc38021 100644 --- a/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt @@ -60,6 +60,7 @@ import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -119,10 +120,12 @@ import com.android.developers.androidify.theme.R as ThemeR fun HomeScreen( homeScreenViewModel: HomeViewModel = hiltViewModel(), isMediumWindowSize: Boolean = isAtLeastMedium(), + isWindowNotOccluded: MutableState, onClickLetsGo: (IntOffset) -> Unit = {}, onAboutClicked: () -> Unit = {}, ) { val state = homeScreenViewModel.state.collectAsStateWithLifecycle() + if (!state.value.isAppActive) { AppInactiveScreen() } else { @@ -130,6 +133,7 @@ fun HomeScreen( state.value.videoLink, state.value.dancingDroidLink, isMediumWindowSize, + isWindowNotOccluded.value, onClickLetsGo, onAboutClicked, ) @@ -141,6 +145,7 @@ fun HomeScreenContents( videoLink: String?, dancingBotLink: String?, isMediumWindowSize: Boolean, + isWindowNotOccluded: Boolean, onClickLetsGo: (IntOffset) -> Unit, onAboutClicked: () -> Unit, ) { @@ -168,6 +173,7 @@ fun HomeScreenContents( ) { VideoPlayerRotatedCard( videoLink, + isWindowNotOccluded, modifier = Modifier .padding(32.dp) .align(Alignment.Center), @@ -198,6 +204,7 @@ fun HomeScreenContents( CompactPager( videoLink, dancingBotLink, + isWindowNotOccluded = isWindowNotOccluded, onClickLetsGo, onAboutClicked, ) @@ -210,6 +217,7 @@ fun HomeScreenContents( private fun CompactPager( videoLink: String?, dancingBotLink: String?, + isWindowNotOccluded: Boolean, onClick: (IntOffset) -> Unit, onAboutClicked: () -> Unit, ) { @@ -239,6 +247,7 @@ private fun CompactPager( Box(modifier = Modifier.fillMaxSize()) { VideoPlayerRotatedCard( videoLink = videoLink, + isWindowNotOccluded = isWindowNotOccluded, modifier = Modifier .padding(horizontal = 32.dp) .align(Alignment.Center), @@ -298,6 +307,7 @@ private fun CompactPager( @Composable private fun VideoPlayerRotatedCard( videoLink: String?, + isWindowNotOccluded: Boolean, modifier: Modifier = Modifier, ) { val aspectRatio = 280f / 380f @@ -316,6 +326,7 @@ private fun VideoPlayerRotatedCard( ) { VideoPlayer( videoLink, + isWindowNotOccluded, modifier = Modifier .aspectRatio(aspectRatio) .align(Alignment.Center) @@ -334,6 +345,7 @@ private fun HomeScreenPhonePreview() { isMediumWindowSize = false, onClickLetsGo = {}, videoLink = "", + isWindowNotOccluded = true, dancingBotLink = "https://services.google.com/fh/files/misc/android_dancing.gif", onAboutClicked = {}, ) @@ -349,6 +361,7 @@ private fun HomeScreenLargeScreensPreview() { isMediumWindowSize = true, onClickLetsGo = { }, videoLink = "", + isWindowNotOccluded = true, dancingBotLink = "https://services.google.com/fh/files/misc/android_dancing.gif", onAboutClicked = {}, ) @@ -516,6 +529,7 @@ private fun DancingBotHeadlineText( @Composable private fun VideoPlayer( videoLink: String?, + isWindowNotOccluded: Boolean, modifier: Modifier = Modifier, ) { if (LocalInspectionMode.current) return // Layoutlib does not support ExoPlayer @@ -547,8 +561,8 @@ private fun VideoPlayer( .then(modifier), ) { player?.let { currentPlayer -> - LaunchedEffect(videoFullyOnScreen) { - if (videoFullyOnScreen) currentPlayer.play() else currentPlayer.pause() + LaunchedEffect(videoFullyOnScreen, isWindowNotOccluded) { + if (videoFullyOnScreen && isWindowNotOccluded) currentPlayer.play() else currentPlayer.pause() } // Render the video From efec32554d38accfc599808434d9b749679c398e Mon Sep 17 00:00:00 2001 From: Francesco Romano Date: Wed, 2 Jul 2025 16:17:47 +0200 Subject: [PATCH 2/2] Update video player logic: now it pauses also when another window is occluding it (in multi-window mode) --- .../developers/androidify/MainActivity.kt | 25 ++++++++++++++++++- .../androidify/navigation/MainNavigation.kt | 5 ++-- .../developers/androidify/home/HomeScreen.kt | 18 +++++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/android/developers/androidify/MainActivity.kt b/app/src/main/java/com/android/developers/androidify/MainActivity.kt index 7b005cf5..fefe6d67 100644 --- a/app/src/main/java/com/android/developers/androidify/MainActivity.kt +++ b/app/src/main/java/com/android/developers/androidify/MainActivity.kt @@ -15,12 +15,16 @@ */ package com.android.developers.androidify +import android.os.Build import android.os.Bundle +import android.view.WindowManager +import android.window.TrustedPresentationThresholds import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.android.developers.androidify.navigation.MainNavigation @@ -31,6 +35,8 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { + private val isWindowNotOccluded = mutableStateOf(true) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -46,8 +52,25 @@ class MainActivity : ComponentActivity() { Color.Transparent.toArgb(), ), ) - MainNavigation() + MainNavigation(isWindowNotOccluded) } } } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + val presentationThreshold = TrustedPresentationThresholds( + 1f, 0.25f, 500 + ) + + val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager + windowManager.registerTrustedPresentationListener( + window.decorView.windowToken, + presentationThreshold, + mainExecutor + ) { notOccluded -> isWindowNotOccluded.value = notOccluded } + } + } + } diff --git a/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt b/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt index 7327d87d..8e82054a 100644 --- a/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt +++ b/app/src/main/java/com/android/developers/androidify/navigation/MainNavigation.kt @@ -26,6 +26,7 @@ import androidx.compose.animation.scaleOut import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -42,12 +43,11 @@ import com.android.developers.androidify.creation.CreationScreen import com.android.developers.androidify.home.AboutScreen import com.android.developers.androidify.home.HomeScreen import com.android.developers.androidify.theme.transitions.ColorSplashTransitionScreen -import com.google.android.gms.oss.licenses.OssLicensesActivity import com.google.android.gms.oss.licenses.OssLicensesMenuActivity @ExperimentalMaterial3ExpressiveApi @Composable -fun MainNavigation() { +fun MainNavigation(isWindowNotOccluded: MutableState) { val backStack = rememberMutableStateListOf(Home) var positionReveal by remember { mutableStateOf(IntOffset.Zero) @@ -80,6 +80,7 @@ fun MainNavigation() { entryProvider = entryProvider { entry { entry -> HomeScreen( + isWindowNotOccluded = isWindowNotOccluded, onClickLetsGo = { positionOffset -> showSplash = true positionReveal = positionOffset diff --git a/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt b/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt index 3de5c427..2cc38021 100644 --- a/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/android/developers/androidify/home/HomeScreen.kt @@ -60,6 +60,7 @@ import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -119,10 +120,12 @@ import com.android.developers.androidify.theme.R as ThemeR fun HomeScreen( homeScreenViewModel: HomeViewModel = hiltViewModel(), isMediumWindowSize: Boolean = isAtLeastMedium(), + isWindowNotOccluded: MutableState, onClickLetsGo: (IntOffset) -> Unit = {}, onAboutClicked: () -> Unit = {}, ) { val state = homeScreenViewModel.state.collectAsStateWithLifecycle() + if (!state.value.isAppActive) { AppInactiveScreen() } else { @@ -130,6 +133,7 @@ fun HomeScreen( state.value.videoLink, state.value.dancingDroidLink, isMediumWindowSize, + isWindowNotOccluded.value, onClickLetsGo, onAboutClicked, ) @@ -141,6 +145,7 @@ fun HomeScreenContents( videoLink: String?, dancingBotLink: String?, isMediumWindowSize: Boolean, + isWindowNotOccluded: Boolean, onClickLetsGo: (IntOffset) -> Unit, onAboutClicked: () -> Unit, ) { @@ -168,6 +173,7 @@ fun HomeScreenContents( ) { VideoPlayerRotatedCard( videoLink, + isWindowNotOccluded, modifier = Modifier .padding(32.dp) .align(Alignment.Center), @@ -198,6 +204,7 @@ fun HomeScreenContents( CompactPager( videoLink, dancingBotLink, + isWindowNotOccluded = isWindowNotOccluded, onClickLetsGo, onAboutClicked, ) @@ -210,6 +217,7 @@ fun HomeScreenContents( private fun CompactPager( videoLink: String?, dancingBotLink: String?, + isWindowNotOccluded: Boolean, onClick: (IntOffset) -> Unit, onAboutClicked: () -> Unit, ) { @@ -239,6 +247,7 @@ private fun CompactPager( Box(modifier = Modifier.fillMaxSize()) { VideoPlayerRotatedCard( videoLink = videoLink, + isWindowNotOccluded = isWindowNotOccluded, modifier = Modifier .padding(horizontal = 32.dp) .align(Alignment.Center), @@ -298,6 +307,7 @@ private fun CompactPager( @Composable private fun VideoPlayerRotatedCard( videoLink: String?, + isWindowNotOccluded: Boolean, modifier: Modifier = Modifier, ) { val aspectRatio = 280f / 380f @@ -316,6 +326,7 @@ private fun VideoPlayerRotatedCard( ) { VideoPlayer( videoLink, + isWindowNotOccluded, modifier = Modifier .aspectRatio(aspectRatio) .align(Alignment.Center) @@ -334,6 +345,7 @@ private fun HomeScreenPhonePreview() { isMediumWindowSize = false, onClickLetsGo = {}, videoLink = "", + isWindowNotOccluded = true, dancingBotLink = "https://services.google.com/fh/files/misc/android_dancing.gif", onAboutClicked = {}, ) @@ -349,6 +361,7 @@ private fun HomeScreenLargeScreensPreview() { isMediumWindowSize = true, onClickLetsGo = { }, videoLink = "", + isWindowNotOccluded = true, dancingBotLink = "https://services.google.com/fh/files/misc/android_dancing.gif", onAboutClicked = {}, ) @@ -516,6 +529,7 @@ private fun DancingBotHeadlineText( @Composable private fun VideoPlayer( videoLink: String?, + isWindowNotOccluded: Boolean, modifier: Modifier = Modifier, ) { if (LocalInspectionMode.current) return // Layoutlib does not support ExoPlayer @@ -547,8 +561,8 @@ private fun VideoPlayer( .then(modifier), ) { player?.let { currentPlayer -> - LaunchedEffect(videoFullyOnScreen) { - if (videoFullyOnScreen) currentPlayer.play() else currentPlayer.pause() + LaunchedEffect(videoFullyOnScreen, isWindowNotOccluded) { + if (videoFullyOnScreen && isWindowNotOccluded) currentPlayer.play() else currentPlayer.pause() } // Render the video