summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt160
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt467
4 files changed, 661 insertions, 66 deletions
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 7119145a1fa8..48c974a33f12 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
@@ -117,6 +117,8 @@ public class NotificationContentView extends FrameLayout implements Notification
protected HybridNotificationView mSingleLineView;
@Nullable public DisposableHandle mContractedBinderHandle;
+ @Nullable public DisposableHandle mExpandedBinderHandle;
+ @Nullable public DisposableHandle mHeadsUpBinderHandle;
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index a5cd2a2de085..c342bcd2706b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -46,6 +46,9 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -286,11 +289,15 @@ constructor(
}
FLAG_CONTENT_VIEW_EXPANDED ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
+ row.privateLayout.mExpandedBinderHandle?.dispose()
+ row.privateLayout.mExpandedBinderHandle = null
row.privateLayout.setExpandedChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
}
FLAG_CONTENT_VIEW_HEADS_UP ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
+ row.privateLayout.mHeadsUpBinderHandle?.dispose()
+ row.privateLayout.mHeadsUpBinderHandle = null
row.privateLayout.setHeadsUpChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
row.privateLayout.setHeadsUpInflatedSmartReplies(null)
@@ -499,17 +506,87 @@ constructor(
}
}
- if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
+ val richOngoingContentModel = inflationProgress.contentModel.richOngoingContentModel
+
+ if (
+ richOngoingContentModel != null &&
+ reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
+ ) {
logger.logAsyncTaskProgress(entry, "inflating RON view")
- inflationProgress.richOngoingNotificationViewHolder =
- inflationProgress.contentModel.richOngoingContentModel?.let {
+ val inflateContractedView = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0
+ val inflateExpandedView = reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0
+ val inflateHeadsUpView = reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0
+
+ inflationProgress.contractedRichOngoingNotificationViewHolder =
+ if (inflateContractedView) {
ronInflater.inflateView(
- contentModel = it,
+ contentModel = richOngoingContentModel,
existingView = row.privateLayout.contractedChild,
entry = entry,
systemUiContext = context,
- parentView = row.privateLayout
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.Contracted
+ )
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.contractedChild,
+ viewType = RichOngoingNotificationViewType.Contracted
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
+ }
+
+ inflationProgress.expandedRichOngoingNotificationViewHolder =
+ if (inflateExpandedView) {
+ ronInflater.inflateView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.expandedChild,
+ entry = entry,
+ systemUiContext = context,
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.Expanded
)
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.expandedChild,
+ viewType = RichOngoingNotificationViewType.Expanded
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
+ }
+
+ inflationProgress.headsUpRichOngoingNotificationViewHolder =
+ if (inflateHeadsUpView) {
+ ronInflater.inflateView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.headsUpChild,
+ entry = entry,
+ systemUiContext = context,
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.HeadsUp
+ )
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.headsUpChild,
+ viewType = RichOngoingNotificationViewType.HeadsUp
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
}
}
@@ -618,7 +695,9 @@ constructor(
var inflatedSmartReplyState: InflatedSmartReplyState? = null
var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
- var richOngoingNotificationViewHolder: InflatedContentViewHolder? = null
+ var contractedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+ var expandedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+ var headsUpRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
// Inflated SingleLineView that lacks the UI State
var inflatedSingleLineView: HybridNotificationView? = null
@@ -1428,14 +1507,21 @@ constructor(
logger.logAsyncTaskProgress(entry, "finishing")
// before updating the content model, stop existing binding if necessary
- val hasRichOngoingContentModel = result.contentModel.richOngoingContentModel != null
- val requestedRichOngoing = reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
- val rejectedRichOngoing = requestedRichOngoing && !hasRichOngoingContentModel
- if (result.richOngoingNotificationViewHolder != null || rejectedRichOngoing) {
+ if (result.contractedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
row.privateLayout.mContractedBinderHandle?.dispose()
row.privateLayout.mContractedBinderHandle = null
}
+ if (result.expandedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+ row.privateLayout.mExpandedBinderHandle?.dispose()
+ row.privateLayout.mExpandedBinderHandle = null
+ }
+
+ if (result.headsUpRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+ row.privateLayout.mHeadsUpBinderHandle?.dispose()
+ row.privateLayout.mHeadsUpBinderHandle = null
+ }
+
// set the content model after disposal and before setting new rich ongoing view
entry.setContentModel(result.contentModel)
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
@@ -1477,19 +1563,53 @@ constructor(
}
}
- // after updating the content model, set the view, then start the new binder
- result.richOngoingNotificationViewHolder?.let { viewHolder ->
- row.privateLayout.contractedChild = viewHolder.view
- row.privateLayout.expandedChild = null
- row.privateLayout.headsUpChild = null
- row.privateLayout.setExpandedInflatedSmartReplies(null)
- row.privateLayout.setHeadsUpInflatedSmartReplies(null)
- row.privateLayout.mContractedBinderHandle =
- viewHolder.binder.setupContentViewBinder()
- row.setExpandable(false)
+ val hasRichOngoingViewHolder =
+ result.contractedRichOngoingNotificationViewHolder != null ||
+ result.expandedRichOngoingNotificationViewHolder != null ||
+ result.headsUpRichOngoingNotificationViewHolder != null
+
+ if (hasRichOngoingViewHolder) {
+ // after updating the content model, set the view, then start the new binder
+ result.contractedRichOngoingNotificationViewHolder?.let { contractedViewHolder ->
+ if (contractedViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.contractedChild = contractedViewHolder.view
+ row.privateLayout.mContractedBinderHandle =
+ contractedViewHolder.binder.setupContentViewBinder()
+ } else if (contractedViewHolder == NullContentView) {
+ row.privateLayout.contractedChild = null
+ }
+ }
+
+ result.expandedRichOngoingNotificationViewHolder?.let { expandedViewHolder ->
+ if (expandedViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.expandedChild = expandedViewHolder.view
+ row.privateLayout.mExpandedBinderHandle =
+ expandedViewHolder.binder.setupContentViewBinder()
+ } else if (expandedViewHolder == NullContentView) {
+ row.privateLayout.expandedChild = null
+ }
+ }
+
+ result.headsUpRichOngoingNotificationViewHolder?.let { headsUpViewHolder ->
+ if (headsUpViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.headsUpChild = headsUpViewHolder.view
+ row.privateLayout.mHeadsUpBinderHandle =
+ headsUpViewHolder.binder.setupContentViewBinder()
+ } else if (headsUpViewHolder == NullContentView) {
+ row.privateLayout.headsUpChild = null
+ }
+ }
+
+ // clean remoteViewCache when we don't keep existing views.
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+
+ // Since RONs don't support smart reply, remove them from HUNs and Expanded.
+ row.privateLayout.setExpandedInflatedSmartReplies(null)
+ row.privateLayout.setHeadsUpInflatedSmartReplies(null)
+
+ row.setExpandable(row.privateLayout.expandedChild != null)
}
Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
index e9c4960a4011..828fc219e773 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
@@ -24,6 +24,9 @@ import android.view.ViewGroup
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel
@@ -39,7 +42,35 @@ fun interface DeferredContentViewBinder {
fun setupContentViewBinder(): DisposableHandle
}
-class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder)
+enum class RichOngoingNotificationViewType {
+ Contracted,
+ Expanded,
+ HeadsUp,
+}
+
+/**
+ * * Supertype of the 3 different possible result types of
+ * [RichOngoingNotificationViewInflater.inflateView].
+ */
+sealed interface ContentViewInflationResult {
+
+ /** Indicates that the content view should be removed if present. */
+ data object NullContentView : ContentViewInflationResult
+
+ /**
+ * Indicates that the content view (which *must be* present) should be unmodified during this
+ * inflation.
+ */
+ data object KeepExistingView : ContentViewInflationResult
+
+ /**
+ * Contains the new view and binder that should replace any existing content view for this slot.
+ */
+ data class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) :
+ ContentViewInflationResult
+}
+
+fun ContentViewInflationResult?.shouldDisposeViewBinder() = this !is KeepExistingView
/**
* Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
@@ -52,7 +83,14 @@ interface RichOngoingNotificationViewInflater {
entry: NotificationEntry,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder?
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult
+
+ fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean
}
@SysUISingleton
@@ -68,8 +106,9 @@ constructor(
entry: NotificationEntry,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder? {
- if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return null
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return NullContentView
val component = viewModelComponentFactory.create(entry)
return when (contentModel) {
is TimerContentModel ->
@@ -77,28 +116,55 @@ constructor(
existingView,
component::createTimerViewModel,
systemUiContext,
- parentView
+ parentView,
+ viewType
)
is StopwatchContentModel -> TODO("Not yet implemented")
}
}
+ override fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean {
+ if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
+ return when (contentModel) {
+ is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
+ is StopwatchContentModel -> TODO("Not yet implemented")
+ }
+ }
+
private fun inflateTimerView(
existingView: View?,
createViewModel: () -> TimerViewModel,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder? {
- if (existingView is TimerView && !existingView.isReinflateNeeded()) return null
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.rich_ongoing_timer_notification,
- parentView,
- /* attachToRoot= */ false
- ) as TimerView
- return InflatedContentViewHolder(newView) {
- TimerViewBinder.bindWhileAttached(newView, createViewModel())
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (existingView is TimerView && !existingView.isReinflateNeeded()) return KeepExistingView
+
+ return when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> {
+ val newView =
+ LayoutInflater.from(systemUiContext)
+ .inflate(
+ R.layout.rich_ongoing_timer_notification,
+ parentView,
+ /* attachToRoot= */ false
+ ) as TimerView
+ InflatedContentViewHolder(newView) {
+ TimerViewBinder.bindWhileAttached(newView, createViewModel())
+ }
+ }
+ RichOngoingNotificationViewType.Expanded,
+ RichOngoingNotificationViewType.HeadsUp -> NullContentView
}
}
+
+ private fun canKeepTimerView(
+ contentModel: TimerContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = true
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 491919a16a4e..30a1214d69d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -35,6 +35,9 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -74,6 +77,7 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -125,8 +129,10 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
): RichOngoingContentModel? = fakeRonContentModel
}
- private var fakeRonViewHolder: InflatedContentViewHolder? = null
- private val fakeRonViewInflater =
+ private var fakeContractedRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeExpandedRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeHeadsUpRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeRonViewInflater =
spy(
object : RichOngoingNotificationViewInflater {
override fun inflateView(
@@ -134,8 +140,20 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
existingView: View?,
entry: NotificationEntry,
systemUiContext: Context,
- parentView: ViewGroup
- ): InflatedContentViewHolder? = fakeRonViewHolder
+ parentView: ViewGroup,
+ viewType: RichOngoingNotificationViewType
+ ): ContentViewInflationResult =
+ when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> fakeContractedRonViewHolder
+ RichOngoingNotificationViewType.Expanded -> fakeExpandedRonViewHolder
+ RichOngoingNotificationViewType.HeadsUp -> fakeHeadsUpRonViewHolder
+ }
+
+ override fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = false
}
)
@@ -149,6 +167,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
.setContentText("Text")
.setStyle(Notification.BigTextStyle().bigText("big text"))
testHelper = NotificationTestHelper(mContext, mDependency)
+ testHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL)
row = spy(testHelper.createRow(builder.build()))
notificationInflater =
NotificationRowContentBinderImpl(
@@ -388,15 +407,62 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@Test
fun testRonModelRequiredForRonView() {
fakeRonContentModel = null
+ val contractedRonView = View(context)
+ val expandedRonView = View(context)
+ val headsUpRonView = View(context)
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = contractedRonView, binder = mock())
+ fakeExpandedRonViewHolder =
+ InflatedContentViewHolder(view = expandedRonView, binder = mock())
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = headsUpRonView, binder = mock())
+
+ // WHEN inflater inflates
+ val contentToInflate =
+ FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+ inflateAndWait(notificationInflater, contentToInflate, row)
+ verifyZeroInteractions(fakeRonViewInflater)
+ }
+
+ @Test
+ fun testRonModelCleansUpRemoteViews() {
val ronView = View(context)
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+
+ val entry = row.entry
+
+ fakeRonContentModel = mock<TimerContentModel>()
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = ronView, binder = mock<DeferredContentViewBinder>())
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
- verify(fakeRonViewInflater, never()).inflateView(any(), any(), any(), any(), any())
+
+ // VERIFY
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_CONTRACTED))
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_EXPANDED))
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_HEADS_UP))
}
@Test
- fun testRonModelTriggersInflationOfRonView() {
+ fun testRonModelCleansUpSmartReplies() {
+ val ronView = View(context)
+
+ val privateLayout = spy(row.privateLayout)
+
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mock<TimerContentModel>()
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // VERIFY
+ verify(privateLayout).setExpandedInflatedSmartReplies(eq(null))
+ verify(privateLayout).setHeadsUpInflatedSmartReplies(eq(null))
+ }
+
+ @Test
+ fun testRonModelTriggersInflationOfContractedRonView() {
val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
val mockBinder = mock<DeferredContentViewBinder>()
@@ -405,18 +471,229 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
val privateLayout = row.privateLayout
fakeRonContentModel = mockRonModel
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
// VERIFY that the inflater is invoked
verify(fakeRonViewInflater)
- .inflateView(eq(mockRonModel), any(), eq(entry), any(), eq(privateLayout))
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.Contracted)
+ )
assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView)
verify(mockBinder).setupContentViewBinder()
}
@Test
- fun ronViewAppliesElementsInOrder() {
+ fun testRonModelTriggersInflationOfExpandedRonView() {
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ val entry = row.entry
+ val privateLayout = row.privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // VERIFY that the inflater is invoked
+ verify(fakeRonViewInflater)
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.Expanded)
+ )
+ assertThat(row.privateLayout.expandedChild).isSameInstanceAs(ronView)
+ verify(mockBinder).setupContentViewBinder()
+ }
+
+ @Test
+ fun testRonModelTriggersInflationOfHeadsUpRonView() {
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ val entry = row.entry
+ val privateLayout = row.privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // VERIFY that the inflater is invoked
+ verify(fakeRonViewInflater)
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.HeadsUp)
+ )
+ assertThat(row.privateLayout.headsUpChild).isSameInstanceAs(ronView)
+ verify(mockBinder).setupContentViewBinder()
+ }
+
+ @Test
+ fun keepExistingViewForContractedRonNotChangingContractedChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mContractedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeContractedRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // THEN do not dispose old contracted binder handle and change contracted child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setContractedChild(any())
+ }
+
+ @Test
+ fun keepExistingViewForExpandedRonNotChangingExpandedChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // THEN do not dispose old expanded binder handle and change expanded child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setExpandedChild(any())
+ }
+
+ @Test
+ fun keepExistingViewForHeadsUpRonNotChangingHeadsUpChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // THEN - do not dispose old heads up binder handle and change heads up child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setHeadsUpChild(any())
+ }
+
+ @Test
+ fun nullContentViewForContractedRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mContractedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeContractedRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setContractedChild(eq(null))
+ }
+ }
+
+ @Test
+ fun nullContentViewForExpandedRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setExpandedChild(eq(null))
+ }
+ }
+
+ @Test
+ fun nullContentViewForHeadsUpRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setHeadsUpChild(eq(null))
+ }
+ }
+
+ @Test
+ fun contractedRonViewAppliesElementsInOrder() {
val oldHandle = mock<DisposableHandle>()
val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
@@ -429,7 +706,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
row.privateLayout = privateLayout
fakeRonContentModel = mockRonModel
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
@@ -443,16 +721,89 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
}
@Test
- fun testRonNotReinflating() {
- val handle0 = mock<DisposableHandle>()
- val handle1 = mock<DisposableHandle>()
+ fun expandedRonViewAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, mockBinder) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setExpandedChild(eq(ronView))
+ verify(mockBinder).setupContentViewBinder()
+ }
+ }
+
+ @Test
+ fun headsUpRonViewAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, mockBinder) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setHeadsUpChild(eq(ronView))
+ verify(mockBinder).setupContentViewBinder()
+ }
+ }
+
+ @Test
+ fun testRonNotReinflating() {
+ val oldContractedBinderHandle = mock<DisposableHandle>()
+ val oldExpandedBinderHandle = mock<DisposableHandle>()
+ val oldHeadsUpBinderHandle = mock<DisposableHandle>()
+
+ val contractedBinderHandle = mock<DisposableHandle>()
+ val expandedBinderHandle = mock<DisposableHandle>()
+ val headsUpBinderHandle = mock<DisposableHandle>()
+
+ val contractedRonView = View(context)
+ val expandedRonView = View(context)
+ val headsUpRonView = View(context)
+
val mockRonModel1 = mock<TimerContentModel>()
val mockRonModel2 = mock<TimerContentModel>()
- val mockBinder1 = mock<DeferredContentViewBinder>()
- doReturn(handle1).whenever(mockBinder1).setupContentViewBinder()
- row.privateLayout.mContractedBinderHandle = handle0
+ val mockContractedViewBinder = mock<DeferredContentViewBinder>()
+ val mockExpandedViewBinder = mock<DeferredContentViewBinder>()
+ val mockHeadsUpViewBinder = mock<DeferredContentViewBinder>()
+
+ doReturn(contractedBinderHandle).whenever(mockContractedViewBinder).setupContentViewBinder()
+ doReturn(expandedBinderHandle).whenever(mockExpandedViewBinder).setupContentViewBinder()
+ doReturn(headsUpBinderHandle).whenever(mockHeadsUpViewBinder).setupContentViewBinder()
+
+ row.privateLayout.mContractedBinderHandle = oldContractedBinderHandle
+ row.privateLayout.mExpandedBinderHandle = oldExpandedBinderHandle
+ row.privateLayout.mHeadsUpBinderHandle = oldHeadsUpBinderHandle
val entry = spy(row.entry)
row.entry = entry
val privateLayout = spy(row.privateLayout)
@@ -460,31 +811,87 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
// WHEN inflater inflates both a model and a view
fakeRonContentModel = mockRonModel1
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder1)
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = contractedRonView, binder = mockContractedViewBinder)
+ fakeExpandedRonViewHolder =
+ InflatedContentViewHolder(view = expandedRonView, binder = mockExpandedViewBinder)
+ fakeHeadsUpRonViewHolder =
+ InflatedContentViewHolder(view = headsUpRonView, binder = mockHeadsUpViewBinder)
+
+ val contentToInflate =
+ FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+ inflateAndWait(notificationInflater, contentToInflate, row)
// Validate that these 4 steps happen in this precise order
- inOrder(handle0, entry, privateLayout, mockBinder1, handle1) {
- verify(handle0).dispose()
+ inOrder(
+ oldContractedBinderHandle,
+ oldExpandedBinderHandle,
+ oldHeadsUpBinderHandle,
+ entry,
+ privateLayout,
+ mockContractedViewBinder,
+ mockExpandedViewBinder,
+ mockHeadsUpViewBinder,
+ contractedBinderHandle,
+ expandedBinderHandle,
+ headsUpBinderHandle
+ ) {
+ verify(oldContractedBinderHandle).dispose()
+ verify(oldExpandedBinderHandle).dispose()
+ verify(oldHeadsUpBinderHandle).dispose()
+
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 })
- verify(privateLayout).setContractedChild(eq(ronView))
- verify(mockBinder1).setupContentViewBinder()
- verify(handle1, never()).dispose()
+
+ verify(privateLayout).setContractedChild(eq(contractedRonView))
+ verify(mockContractedViewBinder).setupContentViewBinder()
+
+ verify(privateLayout).setExpandedChild(eq(expandedRonView))
+ verify(mockExpandedViewBinder).setupContentViewBinder()
+
+ verify(privateLayout).setHeadsUpChild(eq(headsUpRonView))
+ verify(mockHeadsUpViewBinder).setupContentViewBinder()
+
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
}
- clearInvocations(handle0, entry, privateLayout, mockBinder1, handle1)
+ clearInvocations(
+ oldContractedBinderHandle,
+ oldExpandedBinderHandle,
+ oldHeadsUpBinderHandle,
+ entry,
+ privateLayout,
+ mockContractedViewBinder,
+ mockExpandedViewBinder,
+ mockHeadsUpViewBinder,
+ contractedBinderHandle,
+ expandedBinderHandle,
+ headsUpBinderHandle
+ )
// THEN when the inflater inflates just a model
fakeRonContentModel = mockRonModel2
- fakeRonViewHolder = null
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+ fakeContractedRonViewHolder = KeepExistingView
+ fakeExpandedRonViewHolder = KeepExistingView
+ fakeHeadsUpRonViewHolder = KeepExistingView
+
+ inflateAndWait(notificationInflater, contentToInflate, row)
// Validate that for reinflation, the only thing we do us update the model
- verify(handle1, never()).dispose()
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 })
verify(privateLayout, never()).setContractedChild(any())
- verify(mockBinder1, never()).setupContentViewBinder()
- verify(handle1, never()).dispose()
+ verify(privateLayout, never()).setExpandedChild(any())
+ verify(privateLayout, never()).setHeadsUpChild(any())
+ verify(mockContractedViewBinder, never()).setupContentViewBinder()
+ verify(mockExpandedViewBinder, never()).setupContentViewBinder()
+ verify(mockHeadsUpViewBinder, never()).setupContentViewBinder()
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
}
@Test