Merge "Notify the NotificationViewWrappers whether they are shown" into main
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 60e75ff..6528cef3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -118,6 +118,7 @@
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
private NotificationViewWrapper mHeadsUpWrapper;
+ @Nullable private NotificationViewWrapper mShownWrapper = null;
private final HybridGroupManager mHybridGroupManager;
private int mClipTopAmount;
private int mContentHeight;
@@ -417,6 +418,8 @@
mContractedChild = child;
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ // The contracted wrapper has changed. If this is the shown wrapper, we need to update it.
+ updateShownWrapper(mVisibleType);
}
private NotificationViewWrapper getWrapperForView(View child) {
@@ -480,6 +483,8 @@
if (mContainingNotification != null) {
applySystemActions(mExpandedChild, mContainingNotification.getEntry());
}
+ // The expanded wrapper has changed. If this is the shown wrapper, we need to update it.
+ updateShownWrapper(mVisibleType);
}
/**
@@ -530,6 +535,8 @@
if (mContainingNotification != null) {
applySystemActions(mHeadsUpChild, mContainingNotification.getEntry());
}
+ // The heads up wrapper has changed. If this is the shown wrapper, we need to update it.
+ updateShownWrapper(mVisibleType);
}
@Override
@@ -886,6 +893,7 @@
forceUpdateVisibility(VISIBLE_TYPE_EXPANDED, mExpandedChild, mExpandedWrapper);
forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
+ updateShownWrapper(mVisibleType);
fireExpandedVisibleListenerIfVisible();
// forceUpdateVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
@@ -967,6 +975,7 @@
mHeadsUpChild, mHeadsUpWrapper);
updateViewVisibility(visibleType, VISIBLE_TYPE_SINGLELINE,
mSingleLineView, mSingleLineView);
+ updateShownWrapper(visibleType);
fireExpandedVisibleListenerIfVisible();
// updateViewVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
@@ -980,6 +989,28 @@
}
}
+ /**
+ * Called when the currently shown wrapper is potentially affected by a change to the
+ * {mVisibleType} or the user-visibility of this view.
+ *
+ * @see View#isShown()
+ */
+ private void updateShownWrapper(int visibleType) {
+ final NotificationViewWrapper shownWrapper = isShown() ? getVisibleWrapper(visibleType)
+ : null;
+
+ if (mShownWrapper != shownWrapper) {
+ NotificationViewWrapper hiddenWrapper = mShownWrapper;
+ mShownWrapper = shownWrapper;
+ if (hiddenWrapper != null) {
+ hiddenWrapper.onContentShown(false);
+ }
+ if (shownWrapper != null) {
+ shownWrapper.onContentShown(true);
+ }
+ }
+ }
+
private void animateToVisibleType(int visibleType) {
final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
@@ -990,6 +1021,7 @@
mAnimationStartVisibleType = mVisibleType;
shownView.transformFrom(hiddenView);
getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
+ updateShownWrapper(visibleType);
hiddenView.transformTo(shownView, new Runnable() {
@Override
public void run() {
@@ -1837,6 +1869,7 @@
@Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
+ updateShownWrapper(mVisibleType);
if (isVisible) {
fireExpandedVisibleListenerIfVisible();
}
@@ -2217,6 +2250,21 @@
}
@VisibleForTesting
+ protected NotificationViewWrapper getContractedWrapper() {
+ return mContractedWrapper;
+ }
+
+ @VisibleForTesting
+ protected NotificationViewWrapper getExpandedWrapper() {
+ return mExpandedWrapper;
+ }
+
+ @VisibleForTesting
+ protected NotificationViewWrapper getHeadsUpWrapper() {
+ return mHeadsUpWrapper;
+ }
+
+ @VisibleForTesting
protected void setContractedWrapper(NotificationViewWrapper contractedWrapper) {
mContractedWrapper = contractedWrapper;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index acd6cc6..990adf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -68,13 +68,11 @@
}
@Override
- public void setVisible(boolean visible) {
- super.setVisible(visible);
-
+ public void onContentShown(boolean shown) {
+ super.onContentShown(shown);
BigPictureIconManager imageManager = mRow.getBigPictureIconManager();
if (imageManager != null) {
- // TODO(b/283082473) call it a bit earlier for true, as soon as the row starts to expand
- imageManager.onViewShown(visible);
+ imageManager.onViewShown(shown);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index cdf178e..50f3e78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -311,6 +311,17 @@
}
/**
+ * Called when the user-visibility of this content wrapper has changed.
+ *
+ * @param shown true if the content of this wrapper is user-visible, meaning that the wrapped
+ * view and all of its ancestors are visible.
+ *
+ * @see View#isShown()
+ */
+ public void onContentShown(boolean shown) {
+ }
+
+ /**
* Called to indicate this view is removed
*/
public void setRemoved() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index c4baa69..5549fee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -16,13 +16,17 @@
package com.android.systemui.statusbar.notification.row
+import android.annotation.DimenRes
import android.content.res.Resources
import android.os.UserHandle
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
import android.view.NotificationHeaderView
import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
@@ -30,20 +34,21 @@
import com.android.internal.widget.NotificationActionListLayout
import com.android.internal.widget.NotificationExpandButton
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.statusbar.notification.FeedbackIcon
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
-import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -53,89 +58,247 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
class NotificationContentViewTest : SysuiTestCase() {
- private lateinit var view: NotificationContentView
+ private lateinit var row: ExpandableNotificationRow
+ private lateinit var fakeParent: ViewGroup
@Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier
- private val notificationContentMargin =
- mContext.resources.getDimensionPixelSize(R.dimen.notification_content_margin)
+ private val testableResources = mContext.getOrCreateTestableResources()
+ private val contractedHeight =
+ px(com.android.systemui.res.R.dimen.min_notification_layout_height)
+ private val expandedHeight = px(com.android.systemui.res.R.dimen.notification_max_height)
+ private val notificationContentMargin = px(R.dimen.notification_content_margin)
@Before
fun setup() {
initMocks(this)
-
- mDependency.injectMockDependency(MediaOutputDialogFactory::class.java)
-
- view = spy(NotificationContentView(mContext, /* attrs= */ null))
- val row = ExpandableNotificationRow(mContext, /* attrs= */ null)
- row.entry = createMockNotificationEntry(false)
- val spyRow = spy(row)
- doReturn(10).whenever(spyRow).intrinsicHeight
-
- with(view) {
- initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock())
- setContainingNotification(spyRow)
- setHeights(/* smallHeight= */ 10, /* headsUpMaxHeight= */ 20, /* maxHeight= */ 30)
- contractedChild = createViewWithHeight(10)
- expandedChild = createViewWithHeight(20)
- headsUpChild = createViewWithHeight(30)
- measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
- layout(0, 0, view.measuredWidth, view.measuredHeight)
- }
+ fakeParent = FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }
+ row =
+ spy(
+ ExpandableNotificationRow(mContext, /* attrs= */ null).apply {
+ entry = createMockNotificationEntry()
+ }
+ )
+ ViewUtils.attachView(fakeParent)
}
- private fun createViewWithHeight(height: Int) =
- View(mContext, /* attrs= */ null).apply { minimumHeight = height }
+ @After
+ fun teardown() {
+ fakeParent.removeAllViews()
+ ViewUtils.detachView(fakeParent)
+ }
+
+ @Test
+ fun contractedWrapperSelected_whenShadeIsClosed_wrapperNotNotified() {
+ // GIVEN the shade is closed
+ fakeParent.visibility = View.GONE
+
+ // WHEN a collapsed content is created
+ val view = createContentView(isSystemExpanded = false)
+
+ // THEN the contractedWrapper is set
+ assertEquals(view.contractedWrapper, view.visibleWrapper)
+ // AND the contractedWrapper is visible, but NOT shown
+ verify(view.contractedWrapper).setVisible(true)
+ verify(view.contractedWrapper, never()).onContentShown(anyBoolean())
+ }
+
+ @Test
+ fun contractedWrapperSelected_whenShadeIsOpen_wrapperNotified() {
+ // GIVEN the shade is open
+ fakeParent.visibility = View.VISIBLE
+
+ // WHEN a collapsed content is created
+ val view = createContentView(isSystemExpanded = false)
+
+ // THEN the contractedWrapper is set
+ assertEquals(view.contractedWrapper, view.visibleWrapper)
+ // AND the contractedWrapper is visible and shown
+ verify(view.contractedWrapper, Mockito.atLeastOnce()).setVisible(true)
+ verify(view.contractedWrapper, times(1)).onContentShown(true)
+ }
+
+ @Test
+ fun shadeOpens_collapsedWrapperIsSelected_wrapperNotified() {
+ // GIVEN the shade is closed
+ fakeParent.visibility = View.GONE
+ // AND a collapsed content is created
+ val view = createContentView(isSystemExpanded = false).apply { clearInvocations() }
+
+ // WHEN the shade opens
+ fakeParent.visibility = View.VISIBLE
+ view.onVisibilityAggregated(true)
+
+ // THEN the contractedWrapper is set
+ assertEquals(view.contractedWrapper, view.visibleWrapper)
+ // AND the contractedWrapper is shown
+ verify(view.contractedWrapper, times(1)).onContentShown(true)
+ }
+
+ @Test
+ fun shadeCloses_collapsedWrapperIsShown_wrapperNotified() {
+ // GIVEN the shade is closed
+ fakeParent.visibility = View.VISIBLE
+ // AND a collapsed content is created
+ val view = createContentView(isSystemExpanded = false).apply { clearInvocations() }
+
+ // WHEN the shade opens
+ fakeParent.visibility = View.GONE
+ view.onVisibilityAggregated(false)
+
+ // THEN the contractedWrapper is set
+ assertEquals(view.contractedWrapper, view.visibleWrapper)
+ // AND the contractedWrapper is NOT shown
+ verify(view.contractedWrapper, times(1)).onContentShown(false)
+ }
+
+ @Test
+ fun expandedWrapperSelected_whenShadeIsClosed_wrapperNotNotified() {
+ // GIVEN the shade is closed
+ fakeParent.visibility = View.GONE
+
+ // WHEN a system-expanded content is created
+ val view = createContentView(isSystemExpanded = true)
+
+ // THEN the contractedWrapper is set
+ assertEquals(view.expandedWrapper, view.visibleWrapper)
+ // AND the contractedWrapper is visible, but NOT shown
+ verify(view.expandedWrapper, Mockito.atLeastOnce()).setVisible(true)
+ verify(view.expandedWrapper, never()).onContentShown(anyBoolean())
+ }
+
+ @Test
+ fun expandedWrapperSelected_whenShadeIsOpen_wrapperNotified() {
+ // GIVEN the shade is open
+ fakeParent.visibility = View.VISIBLE
+
+ // WHEN an system-expanded content is created
+ val view = createContentView(isSystemExpanded = true)
+
+ // THEN the expandedWrapper is set
+ assertEquals(view.expandedWrapper, view.visibleWrapper)
+ // AND the expandedWrapper is visible and shown
+ verify(view.expandedWrapper, Mockito.atLeastOnce()).setVisible(true)
+ verify(view.expandedWrapper, times(1)).onContentShown(true)
+ }
+
+ @Test
+ fun shadeOpens_expandedWrapperIsSelected_wrapperNotified() {
+ // GIVEN the shade is closed
+ fakeParent.visibility = View.GONE
+ // AND a system-expanded content is created
+ val view = createContentView(isSystemExpanded = true).apply { clearInvocations() }
+
+ // WHEN the shade opens
+ fakeParent.visibility = View.VISIBLE
+ view.onVisibilityAggregated(true)
+
+ // THEN the expandedWrapper is set
+ assertEquals(view.expandedWrapper, view.visibleWrapper)
+ // AND the expandedWrapper is shown
+ verify(view.expandedWrapper, times(1)).onContentShown(true)
+ }
+
+ @Test
+ fun shadeCloses_expandedWrapperIsShown_wrapperNotified() {
+ // GIVEN the shade is open
+ fakeParent.visibility = View.VISIBLE
+ // AND a system-expanded content is created
+ val view = createContentView(isSystemExpanded = true).apply { clearInvocations() }
+
+ // WHEN the shade opens
+ fakeParent.visibility = View.GONE
+ view.onVisibilityAggregated(false)
+
+ // THEN the expandedWrapper is set
+ assertEquals(view.expandedWrapper, view.visibleWrapper)
+ // AND the expandedWrapper is NOT shown
+ verify(view.expandedWrapper, times(1)).onContentShown(false)
+ }
+
+ @Test
+ fun expandCollapsedNotification_expandedWrapperShown() {
+ // GIVEN the shade is open
+ fakeParent.visibility = View.VISIBLE
+ // AND a collapsed content is created
+ val view = createContentView(isSystemExpanded = false).apply { clearInvocations() }
+
+ // WHEN we collapse the notification
+ whenever(row.intrinsicHeight).thenReturn(expandedHeight)
+ view.contentHeight = expandedHeight
+
+ // THEN the wrappers are updated
+ assertEquals(view.expandedWrapper, view.visibleWrapper)
+ verify(view.contractedWrapper, times(1)).onContentShown(false)
+ verify(view.contractedWrapper).setVisible(false)
+ verify(view.expandedWrapper, times(1)).onContentShown(true)
+ verify(view.expandedWrapper).setVisible(true)
+ }
+
+ @Test
+ fun collapseExpandedNotification_expandedWrapperShown() {
+ // GIVEN the shade is open
+ fakeParent.visibility = View.VISIBLE
+ // AND a system-expanded content is created
+ val view = createContentView(isSystemExpanded = true).apply { clearInvocations() }
+
+ // WHEN we collapse the notification
+ whenever(row.intrinsicHeight).thenReturn(contractedHeight)
+ view.contentHeight = contractedHeight
+
+ // THEN the wrappers are updated
+ assertEquals(view.contractedWrapper, view.visibleWrapper)
+ verify(view.expandedWrapper, times(1)).onContentShown(false)
+ verify(view.expandedWrapper).setVisible(false)
+ verify(view.contractedWrapper, times(1)).onContentShown(true)
+ verify(view.contractedWrapper).setVisible(true)
+ }
@Test
fun testSetFeedbackIcon() {
// Given: contractedChild, enpandedChild, and headsUpChild being set
- val mockContracted = createMockNotificationHeaderView()
- val mockExpanded = createMockNotificationHeaderView()
- val mockHeadsUp = createMockNotificationHeaderView()
-
- with(view) {
- contractedChild = mockContracted
- expandedChild = mockExpanded
- headsUpChild = mockHeadsUp
- }
+ val view = createContentView(isSystemExpanded = false)
// When: FeedBackIcon is set
- view.setFeedbackIcon(
+ val icon =
FeedbackIcon(
R.drawable.ic_feedback_alerted,
R.string.notification_feedback_indicator_alerted
)
- )
+ view.setFeedbackIcon(icon)
- // Then: contractedChild, enpandedChild, and headsUpChild should be set to be visible
- verify(mockContracted).visibility = View.VISIBLE
- verify(mockExpanded).visibility = View.VISIBLE
- verify(mockHeadsUp).visibility = View.VISIBLE
+ // Then: contractedChild, enpandedChild, and headsUpChild is updated with the feedbackIcon
+ verify(view.contractedWrapper).setFeedbackIcon(icon)
+ verify(view.expandedWrapper).setFeedbackIcon(icon)
+ verify(view.headsUpWrapper).setFeedbackIcon(icon)
}
- private fun createMockNotificationHeaderView() =
- mock<NotificationHeaderView>().apply {
- whenever(this.findViewById<View>(R.id.feedback)).thenReturn(this)
- whenever(this.context).thenReturn(mContext)
- }
-
@Test
fun testExpandButtonFocusIsCalled() {
val mockContractedEB = mock<NotificationExpandButton>()
- val mockContracted = createMockNotificationHeaderView(mockContractedEB)
+ val mockContracted = createMockNotificationHeaderView(contractedHeight, mockContractedEB)
val mockExpandedEB = mock<NotificationExpandButton>()
- val mockExpanded = createMockNotificationHeaderView(mockExpandedEB)
+ val mockExpanded = createMockNotificationHeaderView(expandedHeight, mockExpandedEB)
val mockHeadsUpEB = mock<NotificationExpandButton>()
- val mockHeadsUp = createMockNotificationHeaderView(mockHeadsUpEB)
+ val mockHeadsUp = createMockNotificationHeaderView(contractedHeight, mockHeadsUpEB)
- // Set up all 3 child forms
- view.contractedChild = mockContracted
- view.expandedChild = mockExpanded
- view.headsUpChild = mockHeadsUp
+ val view =
+ createContentView(
+ isSystemExpanded = false,
+ )
+
+ // Update all 3 child forms
+ view.apply {
+ contractedChild = mockContracted
+ expandedChild = mockExpanded
+ headsUpChild = mockHeadsUp
+
+ expandedWrapper = spy(expandedWrapper)
+ }
// This is required to call requestAccessibilityFocus()
view.setFocusOnVisibilityChange()
@@ -143,35 +306,41 @@
// The following will initialize the view and switch from not visible to expanded.
// (heads-up is actually an alternate form of contracted, hence this enters expanded state)
view.setHeadsUp(true)
+ assertEquals(view.expandedWrapper, view.visibleWrapper)
verify(mockContractedEB, never()).requestAccessibilityFocus()
verify(mockExpandedEB).requestAccessibilityFocus()
verify(mockHeadsUpEB, never()).requestAccessibilityFocus()
}
- private fun createMockNotificationHeaderView(mockExpandedEB: NotificationExpandButton) =
- mock<NotificationHeaderView>().apply {
- whenever(this.animate()).thenReturn(mock())
- whenever(this.findViewById<View>(R.id.expand_button)).thenReturn(mockExpandedEB)
- whenever(this.context).thenReturn(mContext)
- }
+ private fun createMockNotificationHeaderView(
+ height: Int,
+ mockExpandedEB: NotificationExpandButton
+ ) =
+ spy(NotificationHeaderView(mContext, /* attrs= */ null).apply { minimumHeight = height })
+ .apply {
+ whenever(this.animate()).thenReturn(mock())
+ whenever(this.findViewById<View>(R.id.expand_button)).thenReturn(mockExpandedEB)
+ }
@Test
fun testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
- val mockContracted = mock<NotificationHeaderView>()
+ val mockContracted = spy(createViewWithHeight(contractedHeight))
val mockExpandedActions = mock<NotificationActionListLayout>()
- val mockExpanded = mock<NotificationHeaderView>()
+ val mockExpanded = spy(createViewWithHeight(expandedHeight))
whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
val mockHeadsUpActions = mock<NotificationActionListLayout>()
- val mockHeadsUp = mock<NotificationHeaderView>()
+ val mockHeadsUp = spy(createViewWithHeight(contractedHeight))
whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
- with(view) {
- contractedChild = mockContracted
- expandedChild = mockExpanded
- headsUpChild = mockHeadsUp
- }
+ val view =
+ createContentView(
+ isSystemExpanded = false,
+ contractedView = mockContracted,
+ expandedView = mockExpanded,
+ headsUpView = mockHeadsUp
+ )
view.setRemoteInputVisible(true)
@@ -184,21 +353,23 @@
@Test
fun testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
- val mockContracted = mock<NotificationHeaderView>()
+ val mockContracted = spy(createViewWithHeight(contractedHeight))
val mockExpandedActions = mock<NotificationActionListLayout>()
- val mockExpanded = mock<NotificationHeaderView>()
+ val mockExpanded = spy(createViewWithHeight(expandedHeight))
whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
val mockHeadsUpActions = mock<NotificationActionListLayout>()
- val mockHeadsUp = mock<NotificationHeaderView>()
+ val mockHeadsUp = spy(createViewWithHeight(contractedHeight))
whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
- with(view) {
- contractedChild = mockContracted
- expandedChild = mockExpanded
- headsUpChild = mockHeadsUp
- }
+ val view =
+ createContentView(
+ isSystemExpanded = false,
+ contractedView = mockContracted,
+ expandedView = mockExpanded,
+ headsUpView = mockHeadsUp
+ )
view.setRemoteInputVisible(false)
@@ -212,7 +383,7 @@
fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should not be shown for the given NotificationEntry
- val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockNotificationEntry = createMockNotificationEntry()
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
@@ -223,7 +394,9 @@
)
)
.thenReturn(actionListMarginTarget)
- view.setContainingNotification(mockContainingNotification)
+ val view = createContentView(isSystemExpanded = false)
+
+ view.setContainingNotification(mockContainingNotification) // maybe not needed
// When: call NotificationContentView.setExpandedChild() to set the expandedChild
view.expandedChild = mockExpandedChild
@@ -237,7 +410,7 @@
fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should be shown for the given NotificationEntry
- val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ true)
+ val mockNotificationEntry = createMockNotificationEntry()
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
@@ -248,10 +421,12 @@
)
)
.thenReturn(actionListMarginTarget)
+ val view = createContentView(isSystemExpanded = false)
+
view.setContainingNotification(mockContainingNotification)
// Given: controller says bubbles are enabled for the user
- view.setBubblesEnabledForUser(true);
+ view.setBubblesEnabledForUser(true)
// When: call NotificationContentView.setExpandedChild() to set the expandedChild
view.expandedChild = mockExpandedChild
@@ -263,7 +438,7 @@
@Test
fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
- val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockNotificationEntry = createMockNotificationEntry()
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
@@ -274,13 +449,15 @@
)
)
.thenReturn(actionListMarginTarget)
+ val view = createContentView(isSystemExpanded = false)
+
view.setContainingNotification(mockContainingNotification)
view.expandedChild = mockExpandedChild
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
// When: call NotificationContentView.onNotificationUpdated() to update the
// NotificationEntry, which should not show bubble button
- view.onNotificationUpdated(createMockNotificationEntry(/* showButton= */ false))
+ view.onNotificationUpdated(createMockNotificationEntry())
// Then: bottom margin of actionListMarginTarget should not change, still be 20
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
@@ -289,7 +466,7 @@
@Test
fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
- val mockNotificationEntry = createMockNotificationEntry(/* showButton= */ false)
+ val mockNotificationEntry = createMockNotificationEntry()
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
@@ -300,19 +477,20 @@
)
)
.thenReturn(actionListMarginTarget)
+ val view = createContentView(isSystemExpanded = false, expandedView = mockExpandedChild)
+
view.setContainingNotification(mockContainingNotification)
- view.expandedChild = mockExpandedChild
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
// When: call NotificationContentView.onNotificationUpdated() to update the
// NotificationEntry, which should show bubble button
- view.onNotificationUpdated(createMockNotificationEntry(true))
+ view.onNotificationUpdated(createMockNotificationEntry(/*true*/ ))
// Then: no bubble yet
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
// Given: controller says bubbles are enabled for the user
- view.setBubblesEnabledForUser(true);
+ view.setBubblesEnabledForUser(true)
// Then: bottom margin of actionListMarginTarget should not change, still be 20
assertEquals(0, getMarginBottom(actionListMarginTarget))
@@ -321,81 +499,63 @@
@Test
fun onSetAnimationRunning() {
// Given: contractedWrapper, enpandedWrapper, and headsUpWrapper being set
- val mockContracted = mock<NotificationViewWrapper>()
- val mockExpanded = mock<NotificationViewWrapper>()
- val mockHeadsUp = mock<NotificationViewWrapper>()
-
- view.setContractedWrapper(mockContracted)
- view.setExpandedWrapper(mockExpanded)
- view.setHeadsUpWrapper(mockHeadsUp)
+ val view = createContentView(isSystemExpanded = false)
// When: we set content animation running.
assertTrue(view.setContentAnimationRunning(true))
// Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
// called on them.
- verify(mockContracted, times(1)).setAnimationsRunning(true)
- verify(mockExpanded, times(1)).setAnimationsRunning(true)
- verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+ verify(view.contractedWrapper, times(1)).setAnimationsRunning(true)
+ verify(view.expandedWrapper, times(1)).setAnimationsRunning(true)
+ verify(view.headsUpWrapper, times(1)).setAnimationsRunning(true)
// When: we set content animation running true _again_.
assertFalse(view.setContentAnimationRunning(true))
// Then: the children should not have setAnimationRunning called on them again.
// Verify counts number of calls so far on the object, so these still register as 1.
- verify(mockContracted, times(1)).setAnimationsRunning(true)
- verify(mockExpanded, times(1)).setAnimationsRunning(true)
- verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+ verify(view.contractedWrapper, times(1)).setAnimationsRunning(true)
+ verify(view.expandedWrapper, times(1)).setAnimationsRunning(true)
+ verify(view.headsUpWrapper, times(1)).setAnimationsRunning(true)
}
@Test
fun onSetAnimationStopped() {
// Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
- val mockContracted = mock<NotificationViewWrapper>()
- val mockExpanded = mock<NotificationViewWrapper>()
- val mockHeadsUp = mock<NotificationViewWrapper>()
-
- view.setContractedWrapper(mockContracted)
- view.setExpandedWrapper(mockExpanded)
- view.setHeadsUpWrapper(mockHeadsUp)
+ val view = createContentView(isSystemExpanded = false)
// When: we set content animation running.
assertTrue(view.setContentAnimationRunning(true))
// Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
// called on them.
- verify(mockContracted).setAnimationsRunning(true)
- verify(mockExpanded).setAnimationsRunning(true)
- verify(mockHeadsUp).setAnimationsRunning(true)
+ verify(view.contractedWrapper).setAnimationsRunning(true)
+ verify(view.expandedWrapper).setAnimationsRunning(true)
+ verify(view.headsUpWrapper).setAnimationsRunning(true)
// When: we set content animation running false, the state changes, so the function
// returns true.
assertTrue(view.setContentAnimationRunning(false))
// Then: the children have their animations stopped.
- verify(mockContracted).setAnimationsRunning(false)
- verify(mockExpanded).setAnimationsRunning(false)
- verify(mockHeadsUp).setAnimationsRunning(false)
+ verify(view.contractedWrapper).setAnimationsRunning(false)
+ verify(view.expandedWrapper).setAnimationsRunning(false)
+ verify(view.headsUpWrapper).setAnimationsRunning(false)
}
@Test
fun onSetAnimationInitStopped() {
// Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
- val mockContracted = mock<NotificationViewWrapper>()
- val mockExpanded = mock<NotificationViewWrapper>()
- val mockHeadsUp = mock<NotificationViewWrapper>()
-
- view.setContractedWrapper(mockContracted)
- view.setExpandedWrapper(mockExpanded)
- view.setHeadsUpWrapper(mockHeadsUp)
+ val view = createContentView(isSystemExpanded = false)
// When: we try to stop the animations before they've been started.
assertFalse(view.setContentAnimationRunning(false))
// Then: the children should not have setAnimationRunning called on them again.
- verify(mockContracted, never()).setAnimationsRunning(false)
- verify(mockExpanded, never()).setAnimationsRunning(false)
- verify(mockHeadsUp, never()).setAnimationsRunning(false)
+ verify(view.contractedWrapper, never()).setAnimationsRunning(false)
+ verify(view.expandedWrapper, never()).setAnimationsRunning(false)
+ verify(view.headsUpWrapper, never()).setAnimationsRunning(false)
}
private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
@@ -405,7 +565,7 @@
whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
}
- private fun createMockNotificationEntry(showButton: Boolean) =
+ private fun createMockNotificationEntry() =
mock<NotificationEntry>().apply {
whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this))
.thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON)
@@ -426,10 +586,9 @@
}
private fun createMockExpandedChild(notificationEntry: NotificationEntry) =
- mock<ExpandableNotificationRow>().apply {
+ spy(createViewWithHeight(expandedHeight)).apply {
whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock())
whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock())
- whenever(this.entry).thenReturn(notificationEntry)
whenever(this.context).thenReturn(mContext)
val resourcesMock: Resources = mock()
@@ -437,6 +596,56 @@
whenever(this.resources).thenReturn(resourcesMock)
}
+ private fun createContentView(
+ isSystemExpanded: Boolean,
+ contractedView: View = createViewWithHeight(contractedHeight),
+ expandedView: View = createViewWithHeight(expandedHeight),
+ headsUpView: View = createViewWithHeight(contractedHeight),
+ row: ExpandableNotificationRow = this.row
+ ): NotificationContentView {
+ val height = if (isSystemExpanded) expandedHeight else contractedHeight
+ doReturn(height).whenever(row).intrinsicHeight
+
+ return spy(NotificationContentView(mContext, /* attrs= */ null))
+ .apply {
+ initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock())
+ setContainingNotification(row)
+ setHeights(
+ /* smallHeight= */ contractedHeight,
+ /* headsUpMaxHeight= */ contractedHeight,
+ /* maxHeight= */ expandedHeight
+ )
+ contractedChild = contractedView
+ expandedChild = expandedView
+ headsUpChild = headsUpView
+ contractedWrapper = spy(contractedWrapper)
+ expandedWrapper = spy(expandedWrapper)
+ headsUpWrapper = spy(headsUpWrapper)
+
+ if (isSystemExpanded) {
+ contentHeight = expandedHeight
+ }
+ }
+ .also { contentView ->
+ fakeParent.addView(contentView)
+ contentView.mockRequestLayout()
+ }
+ }
+
+ private fun createViewWithHeight(height: Int) =
+ View(mContext, /* attrs= */ null).apply { minimumHeight = height }
+
private fun getMarginBottom(layout: LinearLayout): Int =
(layout.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin
+
+ private fun px(@DimenRes id: Int): Int = testableResources.resources.getDimensionPixelSize(id)
+}
+
+private fun NotificationContentView.mockRequestLayout() {
+ measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ layout(0, 0, measuredWidth, measuredHeight)
+}
+
+private fun NotificationContentView.clearInvocations() {
+ Mockito.clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
}