Merge "New Pipeline: Show media on lockscreen"

This commit is contained in:
TreeHugger Robot
2022-01-07 17:18:39 +00:00
committed by Android (Google) Code Review
14 changed files with 152 additions and 68 deletions

View File

@@ -16,7 +16,7 @@
-->
<!-- Layout for media controls on the lockscreen -->
<com.android.systemui.statusbar.notification.stack.MediaHeaderView
<com.android.systemui.statusbar.notification.stack.MediaContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -27,7 +27,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.MediaHeaderView
import com.android.systemui.statusbar.notification.stack.MediaContainerView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Utils
@@ -96,14 +96,14 @@ class KeyguardMediaController @Inject constructor(
/**
* single pane media container placed at the top of the notifications list
*/
var singlePaneContainer: MediaHeaderView? = null
var singlePaneContainer: MediaContainerView? = null
private set
private var splitShadeContainer: ViewGroup? = null
/**
* Attaches media container in single pane mode, situated at the top of the notifications list
*/
fun attachSinglePaneContainer(mediaView: MediaHeaderView?) {
fun attachSinglePaneContainer(mediaView: MediaContainerView?) {
val needsListener = singlePaneContainer == null
singlePaneContainer = mediaView
if (needsListener) {

View File

@@ -21,6 +21,7 @@ import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
@@ -37,6 +38,7 @@ private var sUsePeopleFiltering: Boolean? = null
/**
* Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config.
*/
@SysUISingleton
class NotificationSectionsFeatureManager @Inject constructor(
val proxy: DeviceConfigProxy,
val context: Context

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.collection.render
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.stack.MediaContainerView
import javax.inject.Inject
@SysUISingleton
class MediaContainerController @Inject constructor(
private val layoutInflater: LayoutInflater
) : NodeController {
override val nodeLabel = "MediaContainer"
var mediaContainerView: MediaContainerView? = null
private set
fun reinflateView(parent: ViewGroup) {
var oldPos = -1
mediaContainerView?.let { _view ->
_view.transientContainer?.removeView(_view)
if (_view.parent === parent) {
oldPos = parent.indexOfChild(_view)
parent.removeView(_view)
}
}
val inflated = layoutInflater.inflate(
R.layout.keyguard_media_container,
parent,
false /* attachToRoot */)
as MediaContainerView
if (oldPos != -1) {
parent.addView(inflated, oldPos)
}
mediaContainerView = inflated
}
override val view: View
get() = mediaContainerView!!
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,6 +33,8 @@ import com.android.systemui.util.traceSection
* need to present in the shade, notably the section headers.
*/
class NodeSpecBuilder(
private val mediaContainerController: MediaContainerController,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val viewBarn: NotifViewBarn
) {
fun buildNodeSpec(
@@ -39,6 +42,13 @@ class NodeSpecBuilder(
notifList: List<ListEntry>
): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") {
val root = NodeSpecImpl(null, rootController)
// The media container should be added as the first child of the root node
// TODO: Perhaps the node spec building process should be more of a pipeline of its own?
if (sectionsFeatureManager.isMediaControlsEnabled()) {
root.children.add(NodeSpecImpl(root, mediaContainerController))
}
var currentSection: NotifSection? = null
val prevSections = mutableSetOf<NotifSection?>()

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render
import android.content.Context
import android.view.View
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -33,13 +34,15 @@ class ShadeViewManager constructor(
context: Context,
listContainer: NotificationListContainer,
private val stackController: NotifStackController,
mediaContainerController: MediaContainerController,
featureManager: NotificationSectionsFeatureManager,
logger: ShadeViewDifferLogger,
private val viewBarn: NotifViewBarn
) {
// We pass a shim view here because the listContainer may not actually have a view associated
// with it and the differ never actually cares about the root node's view.
private val rootController = RootNodeController(listContainer, View(context))
private val specBuilder = NodeSpecBuilder(viewBarn)
private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, viewBarn)
private val viewDiffer = ShadeViewDiffer(rootController, logger)
/** Method for attaching this manager to the pipeline. */
@@ -68,6 +71,8 @@ class ShadeViewManager constructor(
class ShadeViewManagerFactory @Inject constructor(
private val context: Context,
private val logger: ShadeViewDifferLogger,
private val mediaContainerController: MediaContainerController,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val viewBarn: NotifViewBarn
) {
fun create(listContainer: NotificationListContainer, stackController: NotifStackController) =
@@ -75,6 +80,8 @@ class ShadeViewManagerFactory @Inject constructor(
context,
listContainer,
stackController,
mediaContainerController,
sectionsFeatureManager,
logger,
viewBarn)
}

View File

@@ -25,9 +25,9 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
/**
* Root view to insert Lock screen media controls into the notification stack.
*/
public class MediaHeaderView extends ExpandableView {
public class MediaContainerView extends ExpandableView {
public MediaHeaderView(Context context, AttributeSet attrs) {
public MediaContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
}

View File

@@ -16,17 +16,15 @@
package com.android.systemui.statusbar.notification.stack
import android.annotation.ColorInt
import android.annotation.LayoutRes
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.media.KeyguardMediaController
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager
import com.android.systemui.statusbar.notification.dagger.AlertingHeader
@@ -60,6 +58,7 @@ class NotificationSectionsManager @Inject internal constructor(
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val logger: NotificationSectionsLogger,
private val notifPipelineFlags: NotifPipelineFlags,
private val mediaContainerController: MediaContainerController,
@IncomingHeader private val incomingHeaderController: SectionHeaderController,
@PeopleHeader private val peopleHeaderController: SectionHeaderController,
@AlertingHeader private val alertingHeaderController: SectionHeaderController,
@@ -68,7 +67,7 @@ class NotificationSectionsManager @Inject internal constructor(
private val configurationListener = object : ConfigurationController.ConfigurationListener {
override fun onLocaleListChanged() {
reinflateViews(LayoutInflater.from(parent.context))
reinflateViews()
}
}
@@ -91,39 +90,19 @@ class NotificationSectionsManager @Inject internal constructor(
val peopleHeaderView: SectionHeaderView?
get() = peopleHeaderController.headerView
@get:VisibleForTesting
var mediaControlsView: MediaHeaderView? = null
private set
@VisibleForTesting
val mediaControlsView: MediaContainerView?
get() = mediaContainerController.mediaContainerView
/** Must be called before use. */
fun initialize(parent: NotificationStackScrollLayout, layoutInflater: LayoutInflater) {
fun initialize(parent: NotificationStackScrollLayout) {
check(!initialized) { "NotificationSectionsManager already initialized" }
initialized = true
this.parent = parent
reinflateViews(layoutInflater)
reinflateViews()
configurationController.addCallback(configurationListener)
}
private fun <T : ExpandableView> reinflateView(
view: T?,
layoutInflater: LayoutInflater,
@LayoutRes layoutResId: Int
): T {
var oldPos = -1
view?.let {
view.transientContainer?.removeView(view)
if (view.parent === parent) {
oldPos = parent.indexOfChild(view)
parent.removeView(view)
}
}
val inflated = layoutInflater.inflate(layoutResId, parent, false) as T
if (oldPos != -1) {
parent.addView(inflated, oldPos)
}
return inflated
}
fun createSectionsForBuckets(): Array<NotificationSection> =
sectionsFeatureManager.getNotificationBuckets()
.map { NotificationSection(parent, it) }
@@ -132,13 +111,12 @@ class NotificationSectionsManager @Inject internal constructor(
/**
* Reinflates the entire notification header, including all decoration views.
*/
fun reinflateViews(layoutInflater: LayoutInflater) {
fun reinflateViews() {
silentHeaderController.reinflateView(parent)
alertingHeaderController.reinflateView(parent)
peopleHeaderController.reinflateView(parent)
incomingHeaderController.reinflateView(parent)
mediaControlsView =
reinflateView(mediaControlsView, layoutInflater, R.layout.keyguard_media_header)
mediaContainerController.reinflateView(parent)
keyguardMediaController.attachSinglePaneContainer(mediaControlsView)
}

View File

@@ -574,7 +574,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
updateSplitNotificationShade();
mSectionsManager.initialize(this, LayoutInflater.from(context));
mSectionsManager.initialize(this);
mSections = mSectionsManager.createSectionsForBuckets();
mAmbientState = Dependency.get(AmbientState.class);
@@ -665,7 +665,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
inflateFooterView();
inflateEmptyShadeView();
updateFooter();
mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
mSectionsManager.reinflateViews();
}
public void setIsRemoteInputActive(boolean isActive) {
@@ -2228,7 +2228,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
height += viewHeight;
numShownItems++;
if (viewHeight > 0 || !(expandableView instanceof MediaHeaderView)) {
if (viewHeight > 0 || !(expandableView instanceof MediaContainerView)) {
// Only count the media as a notification if it has a positive height.
numShownNotifs++;
}
@@ -3202,7 +3202,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
ignoreChildren = false;
}
childWasSwipedOut |= isFullySwipedOut(row);
} else if (child instanceof MediaHeaderView) {
} else if (child instanceof MediaContainerView) {
childWasSwipedOut = true;
}
if (!childWasSwipedOut) {

View File

@@ -1503,7 +1503,7 @@ public class NotificationStackScrollLayoutController {
* from the keyguard host to the quick settings one.
*/
public int getFullShadeTransitionInset() {
MediaHeaderView view = mKeyguardMediaController.getSinglePaneContainer();
MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
if (view == null || view.getHeight() == 0
|| mStatusBarStateController.getState() != KEYGUARD) {
return 0;

View File

@@ -183,7 +183,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
import com.android.systemui.statusbar.notification.stack.MediaContainerView;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -1581,7 +1581,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (row.isRemoved()) {
continue;
}
} else if (child instanceof MediaHeaderView) {
} else if (child instanceof MediaContainerView) {
if (child.getVisibility() == GONE) {
continue;
}

View File

@@ -25,7 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.MediaHeaderView
import com.android.systemui.statusbar.notification.stack.MediaContainerView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.UniqueObjectHostView
@@ -57,7 +57,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
@JvmField @Rule
val mockito = MockitoJUnit.rule()
private val mediaHeaderView: MediaHeaderView = MediaHeaderView(context, null)
private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
private val hostView = UniqueObjectHostView(context)
private lateinit var keyguardMediaController: KeyguardMediaController
@@ -78,7 +78,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
context,
configurationController
)
keyguardMediaController.attachSinglePaneContainer(mediaHeaderView)
keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
keyguardMediaController.useSplitShade = false
}
@@ -88,7 +88,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
keyguardMediaController.refreshMediaPosition()
assertThat(mediaHeaderView.visibility).isEqualTo(GONE)
assertThat(mediaContainerView.visibility).isEqualTo(GONE)
}
@Test
@@ -102,7 +102,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
private fun testStateVisibility(state: Int, visibility: Int) {
whenever(statusBarStateController.state).thenReturn(state)
keyguardMediaController.refreshMediaPosition()
assertThat(mediaHeaderView.visibility).isEqualTo(visibility)
assertThat(mediaContainerView.visibility).isEqualTo(visibility)
}
@Test
@@ -112,7 +112,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
keyguardMediaController.refreshMediaPosition()
assertThat(mediaHeaderView.visibility).isEqualTo(GONE)
assertThat(mediaContainerView.visibility).isEqualTo(GONE)
}
@Test
@@ -130,7 +130,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
assertThat(splitShadeContainer.visibility).isEqualTo(GONE)
assertThat(mediaHeaderView.visibility).isEqualTo(VISIBLE)
assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
}
@Test
@@ -149,6 +149,6 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
assertTrue("HostView wasn't attached to the single pane container",
mediaHeaderView.childCount == 1)
mediaContainerView.childCount == 1)
}
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -31,18 +32,18 @@ import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@SmallTest
class NodeSpecBuilderTest : SysuiTestCase() {
@Mock
private lateinit var viewBarn: NotifViewBarn
private val mediaContainerController: MediaContainerController = mock()
private val sectionsFeatureManager: NotificationSectionsFeatureManager = mock()
private val viewBarn: NotifViewBarn = mock()
private var rootController: NodeController = buildFakeController("rootController")
private var headerController0: NodeController = buildFakeController("header0")
@@ -66,13 +67,12 @@ class NodeSpecBuilderTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(viewBarn.requireNodeController(any())).thenAnswer {
whenever(mediaContainerController.mediaContainerView).thenReturn(mock())
whenever(viewBarn.requireNodeController(any())).thenAnswer {
fakeViewBarn.getViewByEntry(it.getArgument(0))
}
specBuilder = NodeSpecBuilder(viewBarn)
specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager, viewBarn)
}
@Test
@@ -108,6 +108,30 @@ class NodeSpecBuilderTest : SysuiTestCase() {
@Test
fun testSimpleMapping() {
checkOutput(
// GIVEN a simple flat list of notifications all in the same headerless section
listOf(
notif(0, section0NoHeader),
notif(1, section0NoHeader),
notif(2, section0NoHeader),
notif(3, section0NoHeader)
),
// THEN we output a similarly simple flag list of nodes
tree(
notifNode(0),
notifNode(1),
notifNode(2),
notifNode(3)
)
)
}
@Test
fun testSimpleMappingWithMedia() {
// WHEN media controls are enabled
whenever(sectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true)
checkOutput(
// GIVEN a simple flat list of notifications all in the same headerless section
listOf(
@@ -117,8 +141,9 @@ class NodeSpecBuilderTest : SysuiTestCase() {
notif(3, section0NoHeader)
),
// THEN we output a similarly simple flag list of nodes
// THEN we output a similarly simple flag list of nodes, with media at the top
tree(
node(mediaContainerController),
notifNode(0),
notifNode(1),
notifNode(2),
@@ -333,7 +358,7 @@ private class FakeViewBarn {
private fun buildFakeController(name: String): NodeController {
val controller = Mockito.mock(NodeController::class.java)
`when`(controller.nodeLabel).thenReturn(name)
whenever(controller.nodeLabel).thenReturn(name)
return controller
}

View File

@@ -41,7 +41,6 @@ import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -54,6 +53,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -87,6 +87,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Mock private NotificationRowComponent mNotificationRowComponent;
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@Mock private NotificationSectionsLogger mLogger;
@Mock private MediaContainerController mMediaContainerController;
@Mock private SectionHeaderController mIncomingHeaderController;
@Mock private SectionHeaderController mPeopleHeaderController;
@Mock private SectionHeaderController mAlertingHeaderController;
@@ -114,6 +115,8 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
});
when(mNotificationRowComponent.getActivatableNotificationViewController())
.thenReturn(mActivatableNotificationViewController);
when(mMediaContainerController.getMediaContainerView())
.thenReturn(mock(MediaContainerView.class));
when(mIncomingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
when(mPeopleHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
when(mAlertingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
@@ -126,6 +129,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
mSectionsFeatureManager,
mLogger,
mNotifPipelineFlags,
mMediaContainerController,
mIncomingHeaderController,
mPeopleHeaderController,
mAlertingHeaderController,
@@ -134,7 +138,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
// Required in order for the header inflation to work properly
when(mNssl.generateLayoutParams(any(AttributeSet.class)))
.thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
mSectionsManager.initialize(mNssl);
when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
@@ -142,7 +146,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Test(expected = IllegalStateException.class)
public void testDuplicateInitializeThrows() {
mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
mSectionsManager.initialize(mNssl);
}
@Test