1 /*
<lambda>null2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.systemui.statusbar.notification.row
18
19 import android.annotation.DimenRes
20 import android.content.res.Resources
21 import android.os.UserHandle
22 import android.service.notification.StatusBarNotification
23 import android.testing.TestableLooper
24 import android.testing.ViewUtils
25 import android.view.NotificationHeaderView
26 import android.view.View
27 import android.view.ViewGroup
28 import android.widget.FrameLayout
29 import android.widget.ImageView
30 import android.widget.LinearLayout
31 import androidx.test.ext.junit.runners.AndroidJUnit4
32 import androidx.test.filters.SmallTest
33 import com.android.internal.R
34 import com.android.internal.widget.NotificationActionListLayout
35 import com.android.internal.widget.NotificationExpandButton
36 import com.android.systemui.SysuiTestCase
37 import com.android.systemui.statusbar.notification.FeedbackIcon
38 import com.android.systemui.statusbar.notification.collection.NotificationEntry
39 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
40 import com.android.systemui.util.mockito.any
41 import com.android.systemui.util.mockito.mock
42 import com.android.systemui.util.mockito.whenever
43 import junit.framework.Assert.assertEquals
44 import junit.framework.Assert.assertFalse
45 import junit.framework.Assert.assertTrue
46 import org.junit.After
47 import org.junit.Before
48 import org.junit.Test
49 import org.junit.runner.RunWith
50 import org.mockito.Mock
51 import org.mockito.Mockito
52 import org.mockito.Mockito.anyBoolean
53 import org.mockito.Mockito.anyInt
54 import org.mockito.Mockito.clearInvocations
55 import org.mockito.Mockito.doReturn
56 import org.mockito.Mockito.never
57 import org.mockito.Mockito.spy
58 import org.mockito.Mockito.times
59 import org.mockito.Mockito.verify
60 import org.mockito.MockitoAnnotations.initMocks
61
62 @SmallTest
63 @RunWith(AndroidJUnit4::class)
64 @TestableLooper.RunWithLooper
65 class NotificationContentViewTest : SysuiTestCase() {
66
67 private lateinit var row: ExpandableNotificationRow
68 private lateinit var fakeParent: ViewGroup
69 @Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier
70
71 private val testableResources = mContext.getOrCreateTestableResources()
72 private val contractedHeight =
73 px(com.android.systemui.res.R.dimen.min_notification_layout_height)
74 private val expandedHeight = px(com.android.systemui.res.R.dimen.notification_max_height)
75 private val notificationContentMargin = px(R.dimen.notification_content_margin)
76
77 @Before
78 fun setup() {
79 initMocks(this)
80 fakeParent =
81 spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
82 val mockEntry = createMockNotificationEntry()
83 row =
84 spy(
85 ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
86 entry = mockEntry
87 }
88 )
89 ViewUtils.attachView(fakeParent)
90 }
91
92 @After
93 fun teardown() {
94 fakeParent.removeAllViews()
95 ViewUtils.detachView(fakeParent)
96 }
97
98 @Test
99 fun contractedWrapperSelected_whenShadeIsClosed_wrapperNotNotified() {
100 // GIVEN the shade is closed
101 fakeParent.visibility = View.GONE
102
103 // WHEN a collapsed content is created
104 val view = createContentView(isSystemExpanded = false)
105
106 // THEN the contractedWrapper is set
107 assertEquals(view.contractedWrapper, view.visibleWrapper)
108 // AND the contractedWrapper is visible, but NOT shown
109 verify(view.contractedWrapper).setVisible(true)
110 verify(view.contractedWrapper, never()).onContentShown(anyBoolean())
111 }
112
113 @Test
114 fun contractedWrapperSelected_whenShadeIsOpen_wrapperNotified() {
115 // GIVEN the shade is open
116 fakeParent.visibility = View.VISIBLE
117
118 // WHEN a collapsed content is created
119 val view = createContentView(isSystemExpanded = false)
120
121 // THEN the contractedWrapper is set
122 assertEquals(view.contractedWrapper, view.visibleWrapper)
123 // AND the contractedWrapper is visible and shown
124 verify(view.contractedWrapper, Mockito.atLeastOnce()).setVisible(true)
125 verify(view.contractedWrapper, times(1)).onContentShown(true)
126 }
127
128 @Test
129 fun shadeOpens_collapsedWrapperIsSelected_wrapperNotified() {
130 // GIVEN the shade is closed
131 fakeParent.visibility = View.GONE
132 // AND a collapsed content is created
133 val view = createContentView(isSystemExpanded = false).apply { clearInvocations() }
134
135 // WHEN the shade opens
136 fakeParent.visibility = View.VISIBLE
137 view.onVisibilityAggregated(true)
138
139 // THEN the contractedWrapper is set
140 assertEquals(view.contractedWrapper, view.visibleWrapper)
141 // AND the contractedWrapper is shown
142 verify(view.contractedWrapper, times(1)).onContentShown(true)
143 }
144
145 @Test
146 fun shadeCloses_collapsedWrapperIsShown_wrapperNotified() {
147 // GIVEN the shade is closed
148 fakeParent.visibility = View.VISIBLE
149 // AND a collapsed content is created
150 val view = createContentView(isSystemExpanded = false).apply { clearInvocations() }
151
152 // WHEN the shade opens
153 fakeParent.visibility = View.GONE
154 view.onVisibilityAggregated(false)
155
156 // THEN the contractedWrapper is set
157 assertEquals(view.contractedWrapper, view.visibleWrapper)
158 // AND the contractedWrapper is NOT shown
159 verify(view.contractedWrapper, times(1)).onContentShown(false)
160 }
161
162 @Test
163 fun expandedWrapperSelected_whenShadeIsClosed_wrapperNotNotified() {
164 // GIVEN the shade is closed
165 fakeParent.visibility = View.GONE
166
167 // WHEN a system-expanded content is created
168 val view = createContentView(isSystemExpanded = true)
169
170 // THEN the contractedWrapper is set
171 assertEquals(view.expandedWrapper, view.visibleWrapper)
172 // AND the contractedWrapper is visible, but NOT shown
173 verify(view.expandedWrapper, Mockito.atLeastOnce()).setVisible(true)
174 verify(view.expandedWrapper, never()).onContentShown(anyBoolean())
175 }
176
177 @Test
178 fun expandedWrapperSelected_whenShadeIsOpen_wrapperNotified() {
179 // GIVEN the shade is open
180 fakeParent.visibility = View.VISIBLE
181
182 // WHEN an system-expanded content is created
183 val view = createContentView(isSystemExpanded = true)
184
185 // THEN the expandedWrapper is set
186 assertEquals(view.expandedWrapper, view.visibleWrapper)
187 // AND the expandedWrapper is visible and shown
188 verify(view.expandedWrapper, Mockito.atLeastOnce()).setVisible(true)
189 verify(view.expandedWrapper, times(1)).onContentShown(true)
190 }
191
192 @Test
193 fun shadeOpens_expandedWrapperIsSelected_wrapperNotified() {
194 // GIVEN the shade is closed
195 fakeParent.visibility = View.GONE
196 // AND a system-expanded content is created
197 val view = createContentView(isSystemExpanded = true).apply { clearInvocations() }
198
199 // WHEN the shade opens
200 fakeParent.visibility = View.VISIBLE
201 view.onVisibilityAggregated(true)
202
203 // THEN the expandedWrapper is set
204 assertEquals(view.expandedWrapper, view.visibleWrapper)
205 // AND the expandedWrapper is shown
206 verify(view.expandedWrapper, times(1)).onContentShown(true)
207 }
208
209 @Test
210 fun shadeCloses_expandedWrapperIsShown_wrapperNotified() {
211 // GIVEN the shade is open
212 fakeParent.visibility = View.VISIBLE
213 // AND a system-expanded content is created
214 val view = createContentView(isSystemExpanded = true).apply { clearInvocations() }
215
216 // WHEN the shade opens
217 fakeParent.visibility = View.GONE
218 view.onVisibilityAggregated(false)
219
220 // THEN the expandedWrapper is set
221 assertEquals(view.expandedWrapper, view.visibleWrapper)
222 // AND the expandedWrapper is NOT shown
223 verify(view.expandedWrapper, times(1)).onContentShown(false)
224 }
225
226 @Test
227 fun expandCollapsedNotification_expandedWrapperShown() {
228 // GIVEN the shade is open
229 fakeParent.visibility = View.VISIBLE
230 // AND a collapsed content is created
231 val view = createContentView(isSystemExpanded = false).apply { clearInvocations() }
232
233 // WHEN we collapse the notification
234 whenever(row.intrinsicHeight).thenReturn(expandedHeight)
235 view.contentHeight = expandedHeight
236
237 // THEN the wrappers are updated
238 assertEquals(view.expandedWrapper, view.visibleWrapper)
239 verify(view.contractedWrapper, times(1)).onContentShown(false)
240 verify(view.contractedWrapper).setVisible(false)
241 verify(view.expandedWrapper, times(1)).onContentShown(true)
242 verify(view.expandedWrapper).setVisible(true)
243 }
244
245 @Test
246 fun collapseExpandedNotification_expandedWrapperShown() {
247 // GIVEN the shade is open
248 fakeParent.visibility = View.VISIBLE
249 // AND a system-expanded content is created
250 val view = createContentView(isSystemExpanded = true).apply { clearInvocations() }
251
252 // WHEN we collapse the notification
253 whenever(row.intrinsicHeight).thenReturn(contractedHeight)
254 view.contentHeight = contractedHeight
255
256 // THEN the wrappers are updated
257 assertEquals(view.contractedWrapper, view.visibleWrapper)
258 verify(view.expandedWrapper, times(1)).onContentShown(false)
259 verify(view.expandedWrapper).setVisible(false)
260 verify(view.contractedWrapper, times(1)).onContentShown(true)
261 verify(view.contractedWrapper).setVisible(true)
262 }
263
264 @Test
265 fun testSetFeedbackIcon() {
266 // Given: contractedChild, enpandedChild, and headsUpChild being set
267 val view = createContentView(isSystemExpanded = false)
268
269 // When: FeedBackIcon is set
270 val icon =
271 FeedbackIcon(
272 R.drawable.ic_feedback_alerted,
273 R.string.notification_feedback_indicator_alerted
274 )
275 view.setFeedbackIcon(icon)
276
277 // Then: contractedChild, enpandedChild, and headsUpChild is updated with the feedbackIcon
278 verify(view.contractedWrapper).setFeedbackIcon(icon)
279 verify(view.expandedWrapper).setFeedbackIcon(icon)
280 verify(view.headsUpWrapper).setFeedbackIcon(icon)
281 }
282
283 @Test
284 fun testExpandButtonFocusIsCalled() {
285 val mockContractedEB = mock<NotificationExpandButton>()
286 val mockContracted = createMockNotificationHeaderView(contractedHeight, mockContractedEB)
287
288 val mockExpandedEB = mock<NotificationExpandButton>()
289 val mockExpanded = createMockNotificationHeaderView(expandedHeight, mockExpandedEB)
290
291 val mockHeadsUpEB = mock<NotificationExpandButton>()
292 val mockHeadsUp = createMockNotificationHeaderView(contractedHeight, mockHeadsUpEB)
293
294 val view =
295 createContentView(
296 isSystemExpanded = false,
297 )
298
299 // Update all 3 child forms
300 view.apply {
301 contractedChild = mockContracted
302 expandedChild = mockExpanded
303 headsUpChild = mockHeadsUp
304
305 expandedWrapper = spy(expandedWrapper)
306 }
307
308 // This is required to call requestAccessibilityFocus()
309 view.setFocusOnVisibilityChange()
310
311 // The following will initialize the view and switch from not visible to expanded.
312 // (heads-up is actually an alternate form of contracted, hence this enters expanded state)
313 view.setHeadsUp(true)
314 assertEquals(view.expandedWrapper, view.visibleWrapper)
315 verify(mockContractedEB, never()).requestAccessibilityFocus()
316 verify(mockExpandedEB).requestAccessibilityFocus()
317 verify(mockHeadsUpEB, never()).requestAccessibilityFocus()
318 }
319
320 private fun createMockNotificationHeaderView(
321 height: Int,
322 mockExpandedEB: NotificationExpandButton
323 ) =
324 spy(NotificationHeaderView(mContext, /* attrs= */ null).apply { minimumHeight = height })
325 .apply {
326 whenever(this.animate()).thenReturn(mock())
327 whenever(this.findViewById<View>(R.id.expand_button)).thenReturn(mockExpandedEB)
328 }
329
330 @Test
331 fun testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
332 val mockContracted = spy(createViewWithHeight(contractedHeight))
333
334 val mockExpandedActions = mock<NotificationActionListLayout>()
335 val mockExpanded = spy(createViewWithHeight(expandedHeight))
336 whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
337
338 val mockHeadsUpActions = mock<NotificationActionListLayout>()
339 val mockHeadsUp = spy(createViewWithHeight(contractedHeight))
340 whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
341
342 val view =
343 createContentView(
344 isSystemExpanded = false,
345 contractedView = mockContracted,
346 expandedView = mockExpanded,
347 headsUpView = mockHeadsUp
348 )
349
350 view.setRemoteInputVisible(true)
351
352 verify(mockContracted, never()).findViewById<View>(0)
353 verify(mockExpandedActions).importantForAccessibility =
354 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
355 verify(mockHeadsUpActions).importantForAccessibility =
356 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
357 }
358
359 @Test
360 fun testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
361 val mockContracted = spy(createViewWithHeight(contractedHeight))
362
363 val mockExpandedActions = mock<NotificationActionListLayout>()
364 val mockExpanded = spy(createViewWithHeight(expandedHeight))
365 whenever(mockExpanded.findViewById<View>(R.id.actions)).thenReturn(mockExpandedActions)
366
367 val mockHeadsUpActions = mock<NotificationActionListLayout>()
368 val mockHeadsUp = spy(createViewWithHeight(contractedHeight))
369 whenever(mockHeadsUp.findViewById<View>(R.id.actions)).thenReturn(mockHeadsUpActions)
370
371 val view =
372 createContentView(
373 isSystemExpanded = false,
374 contractedView = mockContracted,
375 expandedView = mockExpanded,
376 headsUpView = mockHeadsUp
377 )
378
379 view.setRemoteInputVisible(false)
380
381 verify(mockContracted, never()).findViewById<View>(0)
382 verify(mockExpandedActions).importantForAccessibility =
383 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
384 verify(mockHeadsUpActions).importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
385 }
386
387 @Test
388 fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
389 // Given: bottom margin of actionListMarginTarget is notificationContentMargin
390 // Bubble button should not be shown for the given NotificationEntry
391 val mockNotificationEntry = createMockNotificationEntry()
392 val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
393 val actionListMarginTarget =
394 spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
395 val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
396 whenever(
397 mockExpandedChild.findViewById<LinearLayout>(
398 R.id.notification_action_list_margin_target
399 )
400 )
401 .thenReturn(actionListMarginTarget)
402 val view = createContentView(isSystemExpanded = false)
403
404 view.setContainingNotification(mockContainingNotification) // maybe not needed
405
406 // When: call NotificationContentView.setExpandedChild() to set the expandedChild
407 view.expandedChild = mockExpandedChild
408
409 // Then: bottom margin of actionListMarginTarget should not change,
410 // still be notificationContentMargin
411 assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
412 }
413
414 @Test
415 fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
416 // Given: bottom margin of actionListMarginTarget is notificationContentMargin
417 // Bubble button should be shown for the given NotificationEntry
418 val mockNotificationEntry = createMockNotificationEntry()
419 val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
420 val actionListMarginTarget =
421 spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
422 val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
423 whenever(
424 mockExpandedChild.findViewById<LinearLayout>(
425 R.id.notification_action_list_margin_target
426 )
427 )
428 .thenReturn(actionListMarginTarget)
429 val view = createContentView(isSystemExpanded = false)
430
431 view.setContainingNotification(mockContainingNotification)
432
433 // Given: controller says bubbles are enabled for the user
434 view.setBubblesEnabledForUser(true)
435
436 // When: call NotificationContentView.setExpandedChild() to set the expandedChild
437 view.expandedChild = mockExpandedChild
438
439 // Then: bottom margin of actionListMarginTarget should be set to 0
440 assertEquals(0, getMarginBottom(actionListMarginTarget))
441 }
442
443 @Test
444 fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
445 // Given: bottom margin of actionListMarginTarget is notificationContentMargin
446 val mockNotificationEntry = createMockNotificationEntry()
447 val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
448 val actionListMarginTarget =
449 spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
450 val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
451 whenever(
452 mockExpandedChild.findViewById<LinearLayout>(
453 R.id.notification_action_list_margin_target
454 )
455 )
456 .thenReturn(actionListMarginTarget)
457 val view = createContentView(isSystemExpanded = false)
458
459 view.setContainingNotification(mockContainingNotification)
460 view.expandedChild = mockExpandedChild
461 assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
462
463 // When: call NotificationContentView.onNotificationUpdated() to update the
464 // NotificationEntry, which should not show bubble button
465 view.onNotificationUpdated(createMockNotificationEntry())
466
467 // Then: bottom margin of actionListMarginTarget should not change, still be 20
468 assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
469 }
470
471 @Test
472 fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
473 // Given: bottom margin of actionListMarginTarget is notificationContentMargin
474 val mockNotificationEntry = createMockNotificationEntry()
475 val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
476 val actionListMarginTarget =
477 spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
478 val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
479 whenever(
480 mockExpandedChild.findViewById<LinearLayout>(
481 R.id.notification_action_list_margin_target
482 )
483 )
484 .thenReturn(actionListMarginTarget)
485 val view = createContentView(isSystemExpanded = false, expandedView = mockExpandedChild)
486
487 view.setContainingNotification(mockContainingNotification)
488 assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
489
490 // When: call NotificationContentView.onNotificationUpdated() to update the
491 // NotificationEntry, which should show bubble button
492 view.onNotificationUpdated(createMockNotificationEntry(/*true*/ ))
493
494 // Then: no bubble yet
495 assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
496
497 // Given: controller says bubbles are enabled for the user
498 view.setBubblesEnabledForUser(true)
499
500 // Then: bottom margin of actionListMarginTarget should not change, still be 20
501 assertEquals(0, getMarginBottom(actionListMarginTarget))
502 }
503
504 @Test
505 fun onSetAnimationRunning() {
506 // Given: contractedWrapper, enpandedWrapper, and headsUpWrapper being set
507 val view = createContentView(isSystemExpanded = false)
508
509 // When: we set content animation running.
510 assertTrue(view.setContentAnimationRunning(true))
511
512 // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
513 // called on them.
514 verify(view.contractedWrapper, times(1)).setAnimationsRunning(true)
515 verify(view.expandedWrapper, times(1)).setAnimationsRunning(true)
516 verify(view.headsUpWrapper, times(1)).setAnimationsRunning(true)
517
518 // When: we set content animation running true _again_.
519 assertFalse(view.setContentAnimationRunning(true))
520
521 // Then: the children should not have setAnimationRunning called on them again.
522 // Verify counts number of calls so far on the object, so these still register as 1.
523 verify(view.contractedWrapper, times(1)).setAnimationsRunning(true)
524 verify(view.expandedWrapper, times(1)).setAnimationsRunning(true)
525 verify(view.headsUpWrapper, times(1)).setAnimationsRunning(true)
526 }
527
528 @Test
529 fun onSetAnimationStopped() {
530 // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
531 val view = createContentView(isSystemExpanded = false)
532
533 // When: we set content animation running.
534 assertTrue(view.setContentAnimationRunning(true))
535
536 // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
537 // called on them.
538 verify(view.contractedWrapper).setAnimationsRunning(true)
539 verify(view.expandedWrapper).setAnimationsRunning(true)
540 verify(view.headsUpWrapper).setAnimationsRunning(true)
541
542 // When: we set content animation running false, the state changes, so the function
543 // returns true.
544 assertTrue(view.setContentAnimationRunning(false))
545
546 // Then: the children have their animations stopped.
547 verify(view.contractedWrapper).setAnimationsRunning(false)
548 verify(view.expandedWrapper).setAnimationsRunning(false)
549 verify(view.headsUpWrapper).setAnimationsRunning(false)
550 }
551
552 @Test
553 fun onSetAnimationInitStopped() {
554 // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
555 val view = createContentView(isSystemExpanded = false)
556
557 // When: we try to stop the animations before they've been started.
558 assertFalse(view.setContentAnimationRunning(false))
559
560 // Then: the children should not have setAnimationRunning called on them again.
561 verify(view.contractedWrapper, never()).setAnimationsRunning(false)
562 verify(view.expandedWrapper, never()).setAnimationsRunning(false)
563 verify(view.headsUpWrapper, never()).setAnimationsRunning(false)
564 }
565
566 @Test
567 fun notifySubtreeAccessibilityStateChanged_notifiesParent() {
568 // Given: a contentView is created
569 val view = createContentView()
570 clearInvocations(fakeParent)
571
572 // When: the contentView is notified for an A11y change
573 view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
574
575 // Then: the contentView propagates the event to its parent
576 verify(fakeParent).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
577 }
578
579 @Test
580 fun notifySubtreeAccessibilityStateChanged_animatingContentView_dontNotifyParent() {
581 // Given: a collapsed contentView is created
582 val view = createContentView()
583 clearInvocations(fakeParent)
584
585 // And: it is animating to expanded
586 view.setAnimationStartVisibleType(NotificationContentView.VISIBLE_TYPE_EXPANDED)
587
588 // When: the contentView is notified for an A11y change
589 view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
590
591 // Then: the contentView DOESN'T propagates the event to its parent
592 verify(fakeParent, never()).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
593 }
594
595 private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
596 mock<ExpandableNotificationRow>().apply {
597 whenever(this.entry).thenReturn(notificationEntry)
598 whenever(this.context).thenReturn(mContext)
599 whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
600 }
601
602 private fun createMockNotificationEntry() =
603 mock<NotificationEntry>().apply {
604 whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this))
605 .thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON)
606 whenever(this.bubbleMetadata).thenReturn(mock())
607 val sbnMock: StatusBarNotification = mock()
608 val userMock: UserHandle = mock()
609 whenever(this.sbn).thenReturn(sbnMock)
610 whenever(sbnMock.user).thenReturn(userMock)
611 }
612
613 private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
614 val outerLayout = LinearLayout(mContext)
615 val innerLayout = LinearLayout(mContext)
616 outerLayout.addView(innerLayout)
617 val mlp = innerLayout.layoutParams as ViewGroup.MarginLayoutParams
618 mlp.setMargins(0, 0, 0, bottomMargin)
619 return innerLayout
620 }
621
622 private fun createMockExpandedChild(notificationEntry: NotificationEntry) =
623 spy(createViewWithHeight(expandedHeight)).apply {
624 whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock())
625 whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock())
626 whenever(this.context).thenReturn(mContext)
627
628 val resourcesMock: Resources = mock()
629 whenever(resourcesMock.configuration).thenReturn(mock())
630 whenever(this.resources).thenReturn(resourcesMock)
631 }
632
633 private fun createContentView(
634 isSystemExpanded: Boolean = false,
635 contractedView: View = createViewWithHeight(contractedHeight),
636 expandedView: View = createViewWithHeight(expandedHeight),
637 headsUpView: View = createViewWithHeight(contractedHeight),
638 row: ExpandableNotificationRow = this.row
639 ): NotificationContentView {
640 val height = if (isSystemExpanded) expandedHeight else contractedHeight
641 doReturn(height).whenever(row).intrinsicHeight
642
643 return spy(NotificationContentView(mContext, /* attrs= */ null))
644 .apply {
645 initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock(), mock())
646 setContainingNotification(row)
647 setHeights(
648 /* smallHeight= */ contractedHeight,
649 /* headsUpMaxHeight= */ contractedHeight,
650 /* maxHeight= */ expandedHeight
651 )
652 contractedChild = contractedView
653 expandedChild = expandedView
654 headsUpChild = headsUpView
655 contractedWrapper = spy(contractedWrapper)
656 expandedWrapper = spy(expandedWrapper)
657 headsUpWrapper = spy(headsUpWrapper)
658
659 if (isSystemExpanded) {
660 contentHeight = expandedHeight
661 }
662 }
663 .also { contentView ->
664 fakeParent.addView(contentView)
665 contentView.mockRequestLayout()
666 }
667 }
668
669 private fun createViewWithHeight(height: Int) =
670 View(mContext, /* attrs= */ null).apply { minimumHeight = height }
671
672 private fun getMarginBottom(layout: LinearLayout): Int =
673 (layout.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin
674
675 private fun px(@DimenRes id: Int): Int = testableResources.resources.getDimensionPixelSize(id)
676 }
677
mockRequestLayoutnull678 private fun NotificationContentView.mockRequestLayout() {
679 measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
680 layout(0, 0, measuredWidth, measuredHeight)
681 }
682
clearInvocationsnull683 private fun NotificationContentView.clearInvocations() {
684 clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
685 }
686