diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 51d160892a013..978998d6ca2c7 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -29,14 +29,14 @@ android:theme="@style/MediaPlayer"> + android:layout_marginBottom="0dp" /> + app:layout_constraintHorizontal_bias="1" + app:layout_constraintHorizontal_chainStyle="packed" /> @@ -188,11 +182,7 @@ android:layout_marginBottom="0dp" android:layout_marginTop="0dp" android:layout_marginStart="0dp" - android:layout_marginEnd="0dp" - app:layout_constraintStart_toEndOf="@id/actionPrev" - app:layout_constraintEnd_toStartOf="@id/actionNext" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/actionPlayPause" /> + android:layout_marginEnd="0dp" /> + android:layout_marginTop="0dp" /> + android:layout_marginTop="0dp" /> + app:layout_constraintHorizontal_chainStyle="packed" /> 4 + + false + left;back,home,recent;right diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 079f5d0a28051..1ae3d362cd6e2 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -102,6 +102,9 @@ one_handed_mode_enabled:onehanded + + true + false diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index dba7290dba09c..fdb56314bcaad 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -990,6 +990,8 @@ 15dp 16dp + 184dp + 128dp 72dp diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml new file mode 100644 index 0000000000000..c6e18a6f8740c --- /dev/null +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml new file mode 100644 index 0000000000000..18ec7aa4cab4c --- /dev/null +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 48f48266fda57..c3f4ce9865969 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -45,7 +45,8 @@ class KeyguardMediaController @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, private val notifLockscreenUserManager: NotificationLockscreenUserManager, private val context: Context, - configurationController: ConfigurationController + configurationController: ConfigurationController, + private val mediaFlags: MediaFlags ) { init { @@ -61,7 +62,11 @@ class KeyguardMediaController @Inject constructor( }) // First let's set the desired state that we want for this host - mediaHost.expansion = MediaHostState.COLLAPSED + mediaHost.expansion = if (mediaFlags.useMediaSessionLayout()) { + MediaHostState.EXPANDED + } else { + MediaHostState.COLLAPSED + } mediaHost.showsOnlyActiveMedia = true mediaHost.falsingProtectionNeeded = true diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index 791a312f341e4..591aad1014bd0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -471,14 +471,16 @@ class MediaViewController @Inject constructor( private fun updateMediaViewControllerType(type: TYPE) { this.type = type + + // These XML resources contain ConstraintSets that will apply to this player type's layout when (type) { TYPE.PLAYER -> { collapsedLayout.load(context, R.xml.media_collapsed) expandedLayout.load(context, R.xml.media_expanded) } TYPE.PLAYER_SESSION -> { - collapsedLayout.clone(context, R.layout.media_session_view) - expandedLayout.clone(context, R.layout.media_session_view) + collapsedLayout.load(context, R.xml.media_session_collapsed) + expandedLayout.load(context, R.xml.media_session_expanded) } TYPE.RECOMMENDATION -> { collapsedLayout.load(context, R.xml.media_recommendation_collapsed) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 2d2fa1f08452e..a3af0e58c43d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -17,12 +17,16 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; +import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; +import androidx.annotation.VisibleForTesting; + import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; +import com.android.systemui.media.MediaFlags; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.qs.QSTile; @@ -31,6 +35,7 @@ import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.statusbar.policy.BrightnessMirrorController; +import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; import java.util.List; @@ -54,11 +59,16 @@ public class QuickQSPanelController extends QSPanelControllerBase private final QuickQSBrightnessController mBrightnessController; private final BrightnessMirrorHandler mBrightnessMirrorHandler; + private final MediaFlags mMediaFlags; + private final boolean mUsingCollapsedLandscapeMedia; + @Inject QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost, QSCustomizerController qsCustomizerController, @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, @Named(QUICK_QS_PANEL) MediaHost mediaHost, + @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia, + MediaFlags mediaFlags, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager, QuickQSBrightnessController quickQSBrightnessController @@ -67,17 +77,36 @@ public class QuickQSPanelController extends QSPanelControllerBase uiEventLogger, qsLogger, dumpManager); mBrightnessController = quickQSBrightnessController; mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); + mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia; + mMediaFlags = mediaFlags; } @Override protected void onInit() { super.onInit(); - mMediaHost.setExpansion(0.0f); + updateMediaExpansion(); mMediaHost.setShowsOnlyActiveMedia(true); mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); mBrightnessController.init(mShouldUseSplitNotificationShade); } + private void updateMediaExpansion() { + int rotation = getRotation(); + boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE + || rotation == RotationUtils.ROTATION_SEASCAPE; + if (mMediaFlags.useMediaSessionLayout() + && (!mUsingCollapsedLandscapeMedia || !isLandscape)) { + mMediaHost.setExpansion(MediaHost.EXPANDED); + } else { + mMediaHost.setExpansion(MediaHost.COLLAPSED); + } + } + + @VisibleForTesting + protected int getRotation() { + return RotationUtils.getRotation(getContext()); + } + @Override protected void onViewAttached() { super.onViewAttached(); @@ -116,6 +145,7 @@ public class QuickQSPanelController extends QSPanelControllerBase @Override protected void onConfigurationChanged() { mBrightnessController.refreshVisibility(mShouldUseSplitNotificationShade); + updateMediaExpansion(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index 776ee1021db28..fdf9ae0d4b635 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.dagger; +import static com.android.systemui.util.Utils.useCollapsedMediaInLandscape; import static com.android.systemui.util.Utils.useQsMediaPlayer; import android.content.Context; @@ -56,6 +57,7 @@ public interface QSFragmentModule { String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer"; String QS_SECURITY_FOOTER_VIEW = "qs_security_footer"; String QS_USING_MEDIA_PLAYER = "qs_using_media_player"; + String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media"; /** * Provide a context themed using the QS theme @@ -171,6 +173,13 @@ public interface QSFragmentModule { return useQsMediaPlayer(context); } + /** */ + @Provides + @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) + static boolean providesQSUsingCollapsedLandscapeMedia(Context context) { + return useCollapsedMediaInLandscape(context.getResources()); + } + /** */ @Provides @QSScope diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 407dc5e2787a7..71d8e33449377 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -161,6 +161,14 @@ public class Utils { return useQsMediaPlayer(context) && flag > 0; } + /** + * Returns true if the device should use the collapsed layout for the media player when in + * landscape (or seascape) orientation + */ + public static boolean useCollapsedMediaInLandscape(Resources resources) { + return resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed); + } + /** * Returns true if the device should use the split notification shade, based on orientation and * screen width. diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt index dc7026da21946..1484c9d11ba61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt @@ -36,6 +36,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever @@ -51,6 +52,8 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private lateinit var statusBarStateController: SysuiStatusBarStateController @Mock private lateinit var configurationController: ConfigurationController + @Mock + private lateinit var mediaFlags: MediaFlags @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager @@ -70,13 +73,15 @@ class KeyguardMediaControllerTest : SysuiTestCase() { .thenReturn(true) whenever(mediaHost.hostView).thenReturn(hostView) hostView.layoutParams = FrameLayout.LayoutParams(100, 100) + whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false) keyguardMediaController = KeyguardMediaController( mediaHost, bypassController, statusBarStateController, notificationLockscreenUserManager, context, - configurationController + configurationController, + mediaFlags ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) keyguardMediaController.useSplitShade = false @@ -150,4 +155,24 @@ class KeyguardMediaControllerTest : SysuiTestCase() { assertTrue("HostView wasn't attached to the single pane container", mediaContainerView.childCount == 1) } + + @Test + fun testNotificationLayout_collapsedPlayer() { + verify(mediaHost).expansion = MediaHostState.COLLAPSED + } + + @Test + fun testSessionLayout_expandedPlayer() { + whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true) + keyguardMediaController = KeyguardMediaController( + mediaHost, + bypassController, + statusBarStateController, + notificationLockscreenUserManager, + context, + configurationController, + mediaFlags + ) + verify(mediaHost).expansion = MediaHostState.EXPANDED + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 09c6d9e86a440..1eb16fd64b854 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -23,11 +23,14 @@ import com.android.internal.logging.MetricsLogger import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.media.MediaFlags import com.android.systemui.media.MediaHost +import com.android.systemui.media.MediaHostState import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.util.leak.RotationUtils import org.junit.After import org.junit.Before import org.junit.Test @@ -55,6 +58,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost @Mock + private lateinit var mediaFlags: MediaFlags + @Mock private lateinit var metricsLogger: MetricsLogger private val uiEventLogger = UiEventLoggerFake() @Mock @@ -71,7 +76,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Captor private lateinit var captor: ArgumentCaptor - private lateinit var controller: QuickQSPanelController + private lateinit var controller: TestQuickQSPanelController @Before fun setUp() { @@ -82,13 +87,16 @@ class QuickQSPanelControllerTest : SysuiTestCase() { `when`(quickQSPanel.dumpableTag).thenReturn("") `when`(quickQSPanel.resources).thenReturn(mContext.resources) `when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView) + `when`(mediaFlags.useMediaSessionLayout()).thenReturn(false) - controller = QuickQSPanelController( + controller = TestQuickQSPanelController( quickQSPanel, qsTileHost, qsCustomizerController, false, mediaHost, + true, + mediaFlags, metricsLogger, uiEventLogger, qsLogger, @@ -133,4 +141,49 @@ class QuickQSPanelControllerTest : SysuiTestCase() { verify(quickQsBrightnessController).refreshVisibility(anyBoolean()) } + + @Test + fun testMediaExpansionUpdatedWhenConfigurationChanged() { + `when`(mediaFlags.useMediaSessionLayout()).thenReturn(true) + + // times(2) because both controller and base controller are registering their listeners + verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture()) + + captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) } + verify(mediaHost).expansion = MediaHostState.EXPANDED + + // Rotate device, verify media size updated + controller.setRotation(RotationUtils.ROTATION_LANDSCAPE) + captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) } + + // times(2) because init will have set to collapsed because the flag was off + verify(mediaHost, times(2)).expansion = MediaHostState.COLLAPSED + } + + class TestQuickQSPanelController( + view: QuickQSPanel, + qsTileHost: QSTileHost, + qsCustomizerController: QSCustomizerController, + usingMediaPlayer: Boolean, + mediaHost: MediaHost, + usingCollapsedLandscapeMedia: Boolean, + mediaFlags: MediaFlags, + metricsLogger: MetricsLogger, + uiEventLogger: UiEventLoggerFake, + qsLogger: QSLogger, + dumpManager: DumpManager, + quickQSBrightnessController: QuickQSBrightnessController + ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer, + mediaHost, usingCollapsedLandscapeMedia, mediaFlags, metricsLogger, uiEventLogger, qsLogger, + dumpManager, quickQSBrightnessController) { + + private var rotation = RotationUtils.ROTATION_NONE + + @Override + override fun getRotation(): Int = rotation + + fun setRotation(newRotation: Int) { + rotation = newRotation + } + } }