Loading packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt +17 −4 Original line number Original line Diff line number Diff line Loading @@ -67,7 +67,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { @Mock lateinit var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit @Mock lateinit var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit @Mock lateinit var closeGuts: (immediate: Boolean) -> Unit @Mock lateinit var closeGuts: (immediate: Boolean) -> Unit @Mock lateinit var falsingManager: FalsingManager @Mock lateinit var falsingManager: FalsingManager @Mock lateinit var onCarouselVisibleToUser: () -> Unit @Mock lateinit var onVisibleCardChanged: () -> Unit @Mock lateinit var logger: MediaUiEventLogger @Mock lateinit var logger: MediaUiEventLogger @Mock lateinit var contentContainer: ViewGroup @Mock lateinit var contentContainer: ViewGroup @Mock lateinit var settingsButton: View @Mock lateinit var settingsButton: View Loading Loading @@ -97,7 +97,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { seekBarUpdateListener, seekBarUpdateListener, closeGuts, closeGuts, falsingManager, falsingManager, onCarouselVisibleToUser, onVisibleCardChanged, logger, logger, ) ) mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth Loading Loading @@ -257,7 +257,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { } } @Test @Test fun testCarouselScrollToNewIndex_onCarouselVisibleToUser() { fun testCarouselScrollToNewIndex_exactScroll_onVisibleCardChanged() { setupMediaContainer(visibleIndex = 0) setupMediaContainer(visibleIndex = 0) whenever(mediaCarousel.relativeScrollX).thenReturn(carouselWidth) whenever(mediaCarousel.relativeScrollX).thenReturn(carouselWidth) mediaCarouselScrollHandler.visibleToUser = true mediaCarouselScrollHandler.visibleToUser = true Loading @@ -266,7 +266,20 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { captor.value.onScrollChange(null, 0, 0, 0, 0) captor.value.onScrollChange(null, 0, 0, 0, 0) verify(onCarouselVisibleToUser).invoke() verify(onVisibleCardChanged).invoke() } @Test fun testCarouselScrollToNewIndex_partialScroll_noCallbackInvoked() { setupMediaContainer(visibleIndex = 0) whenever(mediaCarousel.relativeScrollX).thenReturn(carouselWidth + 15) mediaCarouselScrollHandler.visibleToUser = true val captor = ArgumentCaptor.forClass(View.OnScrollChangeListener::class.java) verify(mediaCarousel).setOnScrollChangeListener(captor.capture()) captor.value.onScrollChange(null, 0, 0, 0, 0) verify(onVisibleCardChanged, never()).invoke() } } private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) { private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) { Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +38 −4 Original line number Original line Diff line number Diff line Loading @@ -60,6 +60,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.ui.controller.MediaPlayerData.visiblePlayerKeys import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.controls.ui.view.MediaScrollView import com.android.systemui.media.controls.ui.view.MediaScrollView Loading Loading @@ -155,6 +156,9 @@ constructor( /** Are we currently disabling scrolling, only allowing the first media session to show */ /** Are we currently disabling scrolling, only allowing the first media session to show */ private var currentlyDisableScrolling: Boolean = false private var currentlyDisableScrolling: Boolean = false /** A key for the last player card that is completely visible */ private var lastFullyVisiblePlayerKey: String? = null /** /** * The desired location where we'll be at the end of the transformation. Usually this matches * The desired location where we'll be at the end of the transformation. Usually this matches * the end location, except when we're still waiting on a state update call. * the end location, except when we're still waiting on a state update call. Loading Loading @@ -336,7 +340,7 @@ constructor( this::updateSeekbarListening, this::updateSeekbarListening, this::closeGuts, this::closeGuts, falsingManager, falsingManager, this::onCarouselVisibleToUser, this::onVisibleCardChanged, logger, logger, ) ) carouselLocale = context.resources.configuration.locales.get(0) carouselLocale = context.resources.configuration.locales.get(0) Loading Loading @@ -1052,18 +1056,48 @@ constructor( layout(0, 0, width, height) layout(0, 0, width, height) } } /** Triggered whenever carousel becomes visible, e.g. on swipe down the notification shade. */ fun onCarouselVisibleToUser() { fun onCarouselVisibleToUser() { if (!enableSuggestedDeviceUi()) { return } onCardVisibilityChanged() } /** Triggered whenever carousel's scroll position changes, revealing a new card. */ fun onVisibleCardChanged() { if (!enableSuggestedDeviceUi()) { return } val newVisiblePlayerKey = visiblePlayerKeys().elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.key if (newVisiblePlayerKey != lastFullyVisiblePlayerKey) { lastFullyVisiblePlayerKey = newVisiblePlayerKey if (newVisiblePlayerKey != null) { onCardVisibilityChanged() } } } /** * Triggered whenever card becomes visible either due to the carousel being visible or the card * visibility changed within the carousel. */ private fun onCardVisibilityChanged() { val isCarouselVisible = mediaCarouselScrollHandler.visibleToUser val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex debugLogger.logCardVisibilityChanged(isCarouselVisible, visibleMediaIndex) if ( if ( !enableSuggestedDeviceUi() || !enableSuggestedDeviceUi() || !mediaCarouselScrollHandler.visibleToUser || !isCarouselVisible || MediaPlayerData.mediaData().all { it.second.resumption } MediaPlayerData.mediaData().all { it.second.resumption } ) { ) { return return } } val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { if (MediaPlayerData.players().size > visibleMediaIndex) { val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex) val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex) mediaControlPanel?.onSuggestionSpaceVisible() mediaControlPanel?.onPanelFullyVisible() } } } } Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt +12 −0 Original line number Original line Diff line number Diff line Loading @@ -109,6 +109,18 @@ constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) { { "media carousel($str1), width: $int1 height: $int2, location:$long1" }, { "media carousel($str1), width: $int1 height: $int2, location:$long1" }, ) ) } } fun logCardVisibilityChanged(carouselVisible: Boolean, visibleMediaIndex: Int) { buffer.log( TAG, LogLevel.DEBUG, { bool1 = carouselVisible int1 = visibleMediaIndex }, { "card visibility changed, isVisible: $bool1, index: $int1" }, ) } } } private const val TAG = "MediaCarouselCtlrLog" private const val TAG = "MediaCarouselCtlrLog" packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +2 −2 Original line number Original line Diff line number Diff line Loading @@ -606,9 +606,9 @@ public class MediaControlPanel { } } /** /** * Should be called when the space that holds device suggestions becomes visible to the user. * Called when the panel becomes fully visible. */ */ public void onSuggestionSpaceVisible() { public void onPanelFullyVisible() { if (!Flags.enableSuggestedDeviceUi()) { if (!Flags.enableSuggestedDeviceUi()) { return; return; } } Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt +5 −2 Original line number Original line Diff line number Diff line Loading @@ -64,7 +64,7 @@ class MediaCarouselScrollHandler( private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit, private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit, private val closeGuts: (immediate: Boolean) -> Unit, private val closeGuts: (immediate: Boolean) -> Unit, private val falsingManager: FalsingManager, private val falsingManager: FalsingManager, private val onCarouselVisibleToUser: () -> Unit, private val onVisibleCardChanged: () -> Unit, private val logger: MediaUiEventLogger, private val logger: MediaUiEventLogger, ) { ) { /** Trace state logger for media carousel visibility */ /** Trace state logger for media carousel visibility */ Loading Loading @@ -548,7 +548,10 @@ class MediaCarouselScrollHandler( val visible = (i == visibleMediaIndex) || ((i == (visibleMediaIndex + 1)) && scrolledIn) val visible = (i == visibleMediaIndex) || ((i == (visibleMediaIndex + 1)) && scrolledIn) view.visibility = if (visible) View.VISIBLE else View.INVISIBLE view.visibility = if (visible) View.VISIBLE else View.INVISIBLE } } onCarouselVisibleToUser() if (!scrolledIn) { // Ignore events with a partial scroll, only proceed if the card is fully visible. onVisibleCardChanged() } } } /** /** Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt +17 −4 Original line number Original line Diff line number Diff line Loading @@ -67,7 +67,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { @Mock lateinit var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit @Mock lateinit var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit @Mock lateinit var closeGuts: (immediate: Boolean) -> Unit @Mock lateinit var closeGuts: (immediate: Boolean) -> Unit @Mock lateinit var falsingManager: FalsingManager @Mock lateinit var falsingManager: FalsingManager @Mock lateinit var onCarouselVisibleToUser: () -> Unit @Mock lateinit var onVisibleCardChanged: () -> Unit @Mock lateinit var logger: MediaUiEventLogger @Mock lateinit var logger: MediaUiEventLogger @Mock lateinit var contentContainer: ViewGroup @Mock lateinit var contentContainer: ViewGroup @Mock lateinit var settingsButton: View @Mock lateinit var settingsButton: View Loading Loading @@ -97,7 +97,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { seekBarUpdateListener, seekBarUpdateListener, closeGuts, closeGuts, falsingManager, falsingManager, onCarouselVisibleToUser, onVisibleCardChanged, logger, logger, ) ) mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth Loading Loading @@ -257,7 +257,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { } } @Test @Test fun testCarouselScrollToNewIndex_onCarouselVisibleToUser() { fun testCarouselScrollToNewIndex_exactScroll_onVisibleCardChanged() { setupMediaContainer(visibleIndex = 0) setupMediaContainer(visibleIndex = 0) whenever(mediaCarousel.relativeScrollX).thenReturn(carouselWidth) whenever(mediaCarousel.relativeScrollX).thenReturn(carouselWidth) mediaCarouselScrollHandler.visibleToUser = true mediaCarouselScrollHandler.visibleToUser = true Loading @@ -266,7 +266,20 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { captor.value.onScrollChange(null, 0, 0, 0, 0) captor.value.onScrollChange(null, 0, 0, 0, 0) verify(onCarouselVisibleToUser).invoke() verify(onVisibleCardChanged).invoke() } @Test fun testCarouselScrollToNewIndex_partialScroll_noCallbackInvoked() { setupMediaContainer(visibleIndex = 0) whenever(mediaCarousel.relativeScrollX).thenReturn(carouselWidth + 15) mediaCarouselScrollHandler.visibleToUser = true val captor = ArgumentCaptor.forClass(View.OnScrollChangeListener::class.java) verify(mediaCarousel).setOnScrollChangeListener(captor.capture()) captor.value.onScrollChange(null, 0, 0, 0, 0) verify(onVisibleCardChanged, never()).invoke() } } private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) { private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) { Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +38 −4 Original line number Original line Diff line number Diff line Loading @@ -60,6 +60,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.ui.controller.MediaPlayerData.visiblePlayerKeys import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.controls.ui.view.MediaScrollView import com.android.systemui.media.controls.ui.view.MediaScrollView Loading Loading @@ -155,6 +156,9 @@ constructor( /** Are we currently disabling scrolling, only allowing the first media session to show */ /** Are we currently disabling scrolling, only allowing the first media session to show */ private var currentlyDisableScrolling: Boolean = false private var currentlyDisableScrolling: Boolean = false /** A key for the last player card that is completely visible */ private var lastFullyVisiblePlayerKey: String? = null /** /** * The desired location where we'll be at the end of the transformation. Usually this matches * The desired location where we'll be at the end of the transformation. Usually this matches * the end location, except when we're still waiting on a state update call. * the end location, except when we're still waiting on a state update call. Loading Loading @@ -336,7 +340,7 @@ constructor( this::updateSeekbarListening, this::updateSeekbarListening, this::closeGuts, this::closeGuts, falsingManager, falsingManager, this::onCarouselVisibleToUser, this::onVisibleCardChanged, logger, logger, ) ) carouselLocale = context.resources.configuration.locales.get(0) carouselLocale = context.resources.configuration.locales.get(0) Loading Loading @@ -1052,18 +1056,48 @@ constructor( layout(0, 0, width, height) layout(0, 0, width, height) } } /** Triggered whenever carousel becomes visible, e.g. on swipe down the notification shade. */ fun onCarouselVisibleToUser() { fun onCarouselVisibleToUser() { if (!enableSuggestedDeviceUi()) { return } onCardVisibilityChanged() } /** Triggered whenever carousel's scroll position changes, revealing a new card. */ fun onVisibleCardChanged() { if (!enableSuggestedDeviceUi()) { return } val newVisiblePlayerKey = visiblePlayerKeys().elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.key if (newVisiblePlayerKey != lastFullyVisiblePlayerKey) { lastFullyVisiblePlayerKey = newVisiblePlayerKey if (newVisiblePlayerKey != null) { onCardVisibilityChanged() } } } /** * Triggered whenever card becomes visible either due to the carousel being visible or the card * visibility changed within the carousel. */ private fun onCardVisibilityChanged() { val isCarouselVisible = mediaCarouselScrollHandler.visibleToUser val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex debugLogger.logCardVisibilityChanged(isCarouselVisible, visibleMediaIndex) if ( if ( !enableSuggestedDeviceUi() || !enableSuggestedDeviceUi() || !mediaCarouselScrollHandler.visibleToUser || !isCarouselVisible || MediaPlayerData.mediaData().all { it.second.resumption } MediaPlayerData.mediaData().all { it.second.resumption } ) { ) { return return } } val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { if (MediaPlayerData.players().size > visibleMediaIndex) { val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex) val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex) mediaControlPanel?.onSuggestionSpaceVisible() mediaControlPanel?.onPanelFullyVisible() } } } } Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt +12 −0 Original line number Original line Diff line number Diff line Loading @@ -109,6 +109,18 @@ constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) { { "media carousel($str1), width: $int1 height: $int2, location:$long1" }, { "media carousel($str1), width: $int1 height: $int2, location:$long1" }, ) ) } } fun logCardVisibilityChanged(carouselVisible: Boolean, visibleMediaIndex: Int) { buffer.log( TAG, LogLevel.DEBUG, { bool1 = carouselVisible int1 = visibleMediaIndex }, { "card visibility changed, isVisible: $bool1, index: $int1" }, ) } } } private const val TAG = "MediaCarouselCtlrLog" private const val TAG = "MediaCarouselCtlrLog"
packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +2 −2 Original line number Original line Diff line number Diff line Loading @@ -606,9 +606,9 @@ public class MediaControlPanel { } } /** /** * Should be called when the space that holds device suggestions becomes visible to the user. * Called when the panel becomes fully visible. */ */ public void onSuggestionSpaceVisible() { public void onPanelFullyVisible() { if (!Flags.enableSuggestedDeviceUi()) { if (!Flags.enableSuggestedDeviceUi()) { return; return; } } Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt +5 −2 Original line number Original line Diff line number Diff line Loading @@ -64,7 +64,7 @@ class MediaCarouselScrollHandler( private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit, private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit, private val closeGuts: (immediate: Boolean) -> Unit, private val closeGuts: (immediate: Boolean) -> Unit, private val falsingManager: FalsingManager, private val falsingManager: FalsingManager, private val onCarouselVisibleToUser: () -> Unit, private val onVisibleCardChanged: () -> Unit, private val logger: MediaUiEventLogger, private val logger: MediaUiEventLogger, ) { ) { /** Trace state logger for media carousel visibility */ /** Trace state logger for media carousel visibility */ Loading Loading @@ -548,7 +548,10 @@ class MediaCarouselScrollHandler( val visible = (i == visibleMediaIndex) || ((i == (visibleMediaIndex + 1)) && scrolledIn) val visible = (i == visibleMediaIndex) || ((i == (visibleMediaIndex + 1)) && scrolledIn) view.visibility = if (visible) View.VISIBLE else View.INVISIBLE view.visibility = if (visible) View.VISIBLE else View.INVISIBLE } } onCarouselVisibleToUser() if (!scrolledIn) { // Ignore events with a partial scroll, only proceed if the card is fully visible. onVisibleCardChanged() } } } /** /** Loading