1 /*
2  * Copyright (C) 2017 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 static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
20 import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG;
21 import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertTrue;
28 import static org.mockito.ArgumentMatchers.any;
29 import static org.mockito.ArgumentMatchers.anyBoolean;
30 import static org.mockito.ArgumentMatchers.anyInt;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.never;
34 import static org.mockito.Mockito.reset;
35 import static org.mockito.Mockito.spy;
36 import static org.mockito.Mockito.times;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.verifyNoMoreInteractions;
39 import static org.mockito.Mockito.when;
40 
41 import android.app.Notification;
42 import android.content.Context;
43 import android.graphics.Color;
44 import android.graphics.drawable.AnimatedVectorDrawable;
45 import android.graphics.drawable.AnimationDrawable;
46 import android.graphics.drawable.Drawable;
47 import android.os.UserHandle;
48 import android.platform.test.annotations.EnableFlags;
49 import android.testing.TestableLooper;
50 import android.testing.TestableLooper.RunWithLooper;
51 import android.util.DisplayMetrics;
52 import android.view.View;
53 import android.widget.ImageView;
54 
55 import androidx.test.ext.junit.runners.AndroidJUnit4;
56 import androidx.test.filters.SmallTest;
57 
58 import com.android.internal.R;
59 import com.android.internal.widget.CachingIconView;
60 import com.android.systemui.SysuiTestCase;
61 import com.android.systemui.SysuiTestableContext;
62 import com.android.systemui.flags.FakeFeatureFlagsClassic;
63 import com.android.systemui.flags.Flags;
64 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
65 import com.android.systemui.plugins.statusbar.StatusBarStateController;
66 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
67 import com.android.systemui.statusbar.notification.FeedbackIcon;
68 import com.android.systemui.statusbar.notification.SourceType;
69 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
70 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
71 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
72 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
73 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
74 import com.android.systemui.statusbar.phone.KeyguardBypassController;
75 
76 import org.junit.Assert;
77 import org.junit.Before;
78 import org.junit.Rule;
79 import org.junit.Test;
80 import org.junit.runner.RunWith;
81 import org.mockito.Mockito;
82 import org.mockito.junit.MockitoJUnit;
83 import org.mockito.junit.MockitoRule;
84 
85 import java.util.Arrays;
86 import java.util.List;
87 import java.util.function.Consumer;
88 
89 @SmallTest
90 @RunWith(AndroidJUnit4.class)
91 @RunWithLooper
92 public class ExpandableNotificationRowTest extends SysuiTestCase {
93 
94     private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
95     private NotificationTestHelper mNotificationTestHelper;
96     @Rule public MockitoRule mockito = MockitoJUnit.rule();
97 
98     @Before
setUp()99     public void setUp() throws Exception {
100         allowTestableLooperAsMainThread();
101         mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
102         mNotificationTestHelper = new NotificationTestHelper(
103                 mContext,
104                 mDependency,
105                 TestableLooper.get(this),
106                 mFeatureFlags);
107         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
108     }
109 
110     @Test
testCanShowHeadsUp_notOnKeyguard_true()111     public void testCanShowHeadsUp_notOnKeyguard_true() throws Exception {
112         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
113 
114         row.setOnKeyguard(false);
115 
116         assertTrue(row.canShowHeadsUp());
117     }
118 
119     @Test
testCanShowHeadsUp_dozing_true()120     public void testCanShowHeadsUp_dozing_true() throws Exception {
121         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
122 
123         StatusBarStateController statusBarStateControllerMock =
124                 mNotificationTestHelper.getStatusBarStateController();
125         when(statusBarStateControllerMock.isDozing()).thenReturn(true);
126 
127         assertTrue(row.canShowHeadsUp());
128     }
129 
130     @Test
testCanShowHeadsUp_bypassEnabled_true()131     public void testCanShowHeadsUp_bypassEnabled_true() throws Exception {
132         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
133 
134         KeyguardBypassController keyguardBypassControllerMock =
135                 mNotificationTestHelper.getKeyguardBypassController();
136         when(keyguardBypassControllerMock.getBypassEnabled()).thenReturn(true);
137 
138         assertTrue(row.canShowHeadsUp());
139     }
140 
141     @Test
testCanShowHeadsUp_stickyAndNotDemoted_true()142     public void testCanShowHeadsUp_stickyAndNotDemoted_true() throws Exception {
143         ExpandableNotificationRow row = mNotificationTestHelper.createStickyRow();
144 
145         assertTrue(row.canShowHeadsUp());
146     }
147 
148     @Test
testCanShowHeadsUp_false()149     public void testCanShowHeadsUp_false() throws Exception {
150         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
151 
152         row.setOnKeyguard(true);
153 
154         StatusBarStateController statusBarStateControllerMock =
155                 mNotificationTestHelper.getStatusBarStateController();
156         when(statusBarStateControllerMock.isDozing()).thenReturn(false);
157 
158         KeyguardBypassController keyguardBypassControllerMock =
159                 mNotificationTestHelper.getKeyguardBypassController();
160         when(keyguardBypassControllerMock.getBypassEnabled()).thenReturn(false);
161 
162         assertFalse(row.canShowHeadsUp());
163     }
164 
165     @Test
testUpdateBackgroundColors_isRecursive()166     public void testUpdateBackgroundColors_isRecursive() throws Exception {
167         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
168         group.setTintColor(Color.RED);
169         group.getChildNotificationAt(0).setTintColor(Color.GREEN);
170         group.getChildNotificationAt(1).setTintColor(Color.BLUE);
171 
172         assertThat(group.getCurrentBackgroundTint()).isEqualTo(Color.RED);
173         assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
174                 .isEqualTo(Color.GREEN);
175         assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
176                 .isEqualTo(Color.BLUE);
177 
178         group.updateBackgroundColors();
179 
180         int resetTint = group.getCurrentBackgroundTint();
181         assertThat(resetTint).isNotEqualTo(Color.RED);
182         assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
183                 .isEqualTo(resetTint);
184         assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
185                 .isEqualTo(resetTint);
186     }
187 
188     @Test
testSetSensitiveOnNotifRowNotifiesOfHeightChange()189     public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
190         // GIVEN a sensitive notification row that's currently redacted
191         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
192         measureAndLayout(row);
193         row.setHideSensitiveForIntrinsicHeight(true);
194         row.setSensitive(true, true);
195         assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout());
196         assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
197 
198         // GIVEN that the row has a height change listener
199         OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
200         row.setOnHeightChangedListener(listener);
201 
202         // WHEN the row is set to no longer be sensitive
203         row.setSensitive(false, true);
204 
205         // VERIFY that the height change listener is invoked
206         assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
207         assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
208         verify(listener).onHeightChanged(eq(row), eq(true));
209     }
210 
211     @Test
testSetSensitiveOnGroupRowNotifiesOfHeightChange()212     public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() throws Exception {
213         // GIVEN a sensitive group row that's currently redacted
214         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
215         measureAndLayout(group);
216         group.setHideSensitiveForIntrinsicHeight(true);
217         group.setSensitive(true, true);
218         assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPublicLayout());
219         assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
220 
221         // GIVEN that the row has a height change listener
222         OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
223         group.setOnHeightChangedListener(listener);
224 
225         // WHEN the row is set to no longer be sensitive
226         group.setSensitive(false, true);
227 
228         // VERIFY that the height change listener is invoked
229         assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
230         assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
231         verify(listener).onHeightChanged(eq(group), eq(true));
232     }
233 
234     @Test
testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange()235     public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() throws Exception {
236         // create a notification row whose public version is identical
237         Notification publicNotif = mNotificationTestHelper.createNotification();
238         publicNotif.publicVersion = mNotificationTestHelper.createNotification();
239         ExpandableNotificationRow publicRow = mNotificationTestHelper.createRow(publicNotif);
240 
241         // GIVEN a sensitive public row that's currently redacted
242         measureAndLayout(publicRow);
243         publicRow.setHideSensitiveForIntrinsicHeight(true);
244         publicRow.setSensitive(true, true);
245         assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPublicLayout());
246         assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
247 
248         // GIVEN that the row has a height change listener
249         OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
250         publicRow.setOnHeightChangedListener(listener);
251 
252         // WHEN the row is set to no longer be sensitive
253         publicRow.setSensitive(false, true);
254 
255         // VERIFY that the height change listener is not invoked, because the height didn't change
256         assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPrivateLayout());
257         assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
258         assertThat(publicRow.getPrivateLayout().getMinHeight())
259                 .isEqualTo(publicRow.getPublicLayout().getMinHeight());
260         verify(listener, never()).onHeightChanged(eq(publicRow), anyBoolean());
261     }
262 
measureAndLayout(ExpandableNotificationRow row)263     private void measureAndLayout(ExpandableNotificationRow row) {
264         DisplayMetrics dm = new DisplayMetrics();
265         getContext().getDisplay().getRealMetrics(dm);
266         int width = (int) Math.ceil(400f * dm.density);
267         int height = (int) Math.ceil(600f * dm.density);
268 
269         row.measure(
270                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
271                 View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.UNSPECIFIED)
272         );
273         row.layout(0, 0, row.getMeasuredWidth(), row.getMeasuredHeight());
274     }
275 
276     @Test
testGroupSummaryNotShowingIconWhenPublic()277     public void testGroupSummaryNotShowingIconWhenPublic() throws Exception {
278         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
279 
280         group.setSensitive(true, true);
281         group.setHideSensitiveForIntrinsicHeight(true);
282         assertTrue(group.isSummaryWithChildren());
283         assertFalse(group.isShowingIcon());
284     }
285 
286     @Test
testNotificationHeaderVisibleWhenAnimating()287     public void testNotificationHeaderVisibleWhenAnimating() throws Exception {
288         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
289 
290         group.setSensitive(true, true);
291         group.setHideSensitive(true, false, 0, 0);
292         group.setHideSensitive(false, true, 0, 0);
293         assertEquals(View.VISIBLE, group.getChildrenContainer().getVisibleWrapper()
294                 .getNotificationHeader().getVisibility());
295     }
296 
297     @Test
testUserLockedResetEvenWhenNoChildren()298     public void testUserLockedResetEvenWhenNoChildren() throws Exception {
299         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
300 
301         group.setUserLocked(true);
302         group.setUserLocked(false);
303         assertFalse("The childrencontainer should not be userlocked but is, the state "
304                 + "seems out of sync.", group.getChildrenContainer().isUserLocked());
305     }
306 
307     @Test
308     @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME)
setHideSensitive_shouldNotDisturbAnimation()309     public void setHideSensitive_shouldNotDisturbAnimation() throws Exception {
310         //Given: A row that is during alpha animation
311         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
312 
313         assertEquals(row.getPrivateLayout(), row.getContentView());
314         row.setContentAlpha(0.5f);
315 
316         //When: Set its hideSensitive without changing the content view to show
317         row.setHideSensitive(
318                 /* hideSensitive= */ false,
319                 /* animated= */ false,
320                 /* delay=  */ 0L,
321                 /* duration=  */ 0L
322         );
323         assertEquals(row.getPrivateLayout(), row.getContentView());
324 
325         //Then: The alpha value should not be reset
326         assertEquals(0.5f, row.getPrivateLayout().getAlpha(), 0);
327     }
328 
329     @Test
330     @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME)
setHideSensitive_changeContent_shouldNotDisturbAnimation()331     public void setHideSensitive_changeContent_shouldNotDisturbAnimation() throws Exception {
332 
333         // Given: A sensitive row that has public version but is not hiding sensitive,
334         // and is during an animation that sets its alpha value to be 0.5f
335         Notification publicNotif = mNotificationTestHelper.createNotification();
336         publicNotif.publicVersion = mNotificationTestHelper.createNotification();
337         ExpandableNotificationRow row = mNotificationTestHelper.createRow(publicNotif);
338         row.setSensitive(true, false);
339         row.setContentAlpha(0.5f);
340 
341         assertEquals(0.5f, row.getPrivateLayout().getAlpha(), 0);
342         assertEquals(View.VISIBLE, row.getPrivateLayout().getVisibility());
343 
344         // When: Change its hideSensitive and changes the content view to show the public version
345         row.setHideSensitive(
346                 /* hideSensitive= */ true,
347                 /* animated= */ false,
348                 /* delay=  */ 0L,
349                 /* duration=  */ 0L
350         );
351 
352         // Then: The alpha value of private layout should be reset to 1, private layout be
353         // INVISIBLE;
354         // The alpha value of public layout should be 0.5 to preserve the animation state, public
355         // layout should be VISIBLE
356         assertEquals(View.INVISIBLE, row.getPrivateLayout().getVisibility());
357         assertEquals(1f, row.getPrivateLayout().getAlpha(), 0);
358         assertEquals(View.VISIBLE, row.getPublicLayout().getVisibility());
359         assertEquals(0.5f, row.getPublicLayout().getAlpha(), 0);
360     }
361 
362     @Test
testReinflatedOnDensityChange()363     public void testReinflatedOnDensityChange() throws Exception {
364         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
365         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
366         row.setChildrenContainer(mockContainer);
367 
368         row.onDensityOrFontScaleChanged();
369 
370         verify(mockContainer).reInflateViews(any(), any());
371     }
372 
373     @Test
testIconColorShouldBeUpdatedWhenSensitive()374     public void testIconColorShouldBeUpdatedWhenSensitive() throws Exception {
375         ExpandableNotificationRow row = spy(mNotificationTestHelper.createRow(
376                 FLAG_CONTENT_VIEW_ALL));
377         row.setSensitive(true, true);
378         row.setHideSensitive(true, false, 0, 0);
379         verify(row).updateShelfIconColor();
380     }
381 
382     @Test
testAboveShelfChangedListenerCalled()383     public void testAboveShelfChangedListenerCalled() throws Exception {
384         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
385         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
386         row.setAboveShelfChangedListener(listener);
387         row.setHeadsUp(true);
388         verify(listener).onAboveShelfStateChanged(true);
389     }
390 
391     @Test
testAboveShelfChangedListenerCalledPinned()392     public void testAboveShelfChangedListenerCalledPinned() throws Exception {
393         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
394         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
395         row.setAboveShelfChangedListener(listener);
396         row.setPinned(true);
397         verify(listener).onAboveShelfStateChanged(true);
398     }
399 
400     @Test
testAboveShelfChangedListenerCalledHeadsUpAnimatingAway()401     public void testAboveShelfChangedListenerCalledHeadsUpAnimatingAway() throws Exception {
402         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
403         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
404         row.setAboveShelfChangedListener(listener);
405         row.setHeadsUpAnimatingAway(true);
406         verify(listener).onAboveShelfStateChanged(true);
407     }
408     @Test
testAboveShelfChangedListenerCalledWhenGoingBelow()409     public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception {
410         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
411         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
412         row.setAboveShelfChangedListener(listener);
413         Mockito.reset(listener);
414         row.setHeadsUp(true);
415         row.setAboveShelf(false);
416         verify(listener).onAboveShelfStateChanged(false);
417     }
418 
419     @Test
testClickSound()420     public void testClickSound() throws Exception {
421         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
422 
423         assertTrue("Should play sounds by default.", group.isSoundEffectsEnabled());
424         StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController();
425         when(mock.isDozing()).thenReturn(true);
426         group.setSecureStateProvider(()-> false);
427         assertFalse("Shouldn't play sounds when dark and trusted.",
428                 group.isSoundEffectsEnabled());
429         group.setSecureStateProvider(()-> true);
430         assertTrue("Should always play sounds when not trusted.",
431                 group.isSoundEffectsEnabled());
432     }
433 
434     @Test
testSetDismissed_longPressListenerRemoved()435     public void testSetDismissed_longPressListenerRemoved() throws Exception {
436         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
437 
438         ExpandableNotificationRow.LongPressListener listener =
439                 mock(ExpandableNotificationRow.LongPressListener.class);
440         group.setLongPressListener(listener);
441         group.doLongClickCallback(0, 0);
442         verify(listener, times(1)).onLongPress(eq(group), eq(0), eq(0),
443                 any(NotificationMenuRowPlugin.MenuItem.class));
444         reset(listener);
445 
446         group.dismiss(true);
447         group.doLongClickCallback(0, 0);
448         verify(listener, times(0)).onLongPress(eq(group), eq(0), eq(0),
449                 any(NotificationMenuRowPlugin.MenuItem.class));
450     }
451 
452     @Test
testFeedback_noHeader()453     public void testFeedback_noHeader() throws Exception {
454         ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
455 
456         // public notification is custom layout - no header
457         groupRow.setSensitive(true, true);
458         groupRow.setOnFeedbackClickListener(null);
459         groupRow.setFeedbackIcon(null);
460     }
461 
462     @Test
testFeedback_header()463     public void testFeedback_header() throws Exception {
464         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
465 
466         NotificationContentView publicLayout = mock(NotificationContentView.class);
467         group.setPublicLayout(publicLayout);
468         NotificationContentView privateLayout = mock(NotificationContentView.class);
469         group.setPrivateLayout(privateLayout);
470         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
471         when(mockContainer.getNotificationChildCount()).thenReturn(1);
472         group.setChildrenContainer(mockContainer);
473 
474         final boolean show = true;
475         final FeedbackIcon icon = new FeedbackIcon(
476                 R.drawable.ic_feedback_alerted, R.string.notification_feedback_indicator_alerted);
477         group.setFeedbackIcon(icon);
478 
479         verify(mockContainer, times(1)).setFeedbackIcon(icon);
480         verify(privateLayout, times(1)).setFeedbackIcon(icon);
481         verify(publicLayout, times(1)).setFeedbackIcon(icon);
482     }
483 
484     @Test
testFeedbackOnClick()485     public void testFeedbackOnClick() throws Exception {
486         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
487 
488         ExpandableNotificationRow.CoordinateOnClickListener l = mock(
489                 ExpandableNotificationRow.CoordinateOnClickListener.class);
490         View view = mock(View.class);
491 
492         group.setOnFeedbackClickListener(l);
493 
494         group.getFeedbackOnClickListener().onClick(view);
495         verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
496     }
497 
498     @Test
testHeadsUpAnimatingAwayListener()499     public void testHeadsUpAnimatingAwayListener() throws Exception {
500         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
501         Consumer<Boolean> headsUpListener = mock(Consumer.class);
502         AboveShelfChangedListener aboveShelfChangedListener = mock(AboveShelfChangedListener.class);
503         group.setHeadsUpAnimatingAwayListener(headsUpListener);
504         group.setAboveShelfChangedListener(aboveShelfChangedListener);
505 
506         group.setHeadsUpAnimatingAway(true);
507         verify(headsUpListener).accept(true);
508         verify(aboveShelfChangedListener).onAboveShelfStateChanged(true);
509 
510         group.setHeadsUpAnimatingAway(false);
511         verify(headsUpListener).accept(false);
512         verify(aboveShelfChangedListener).onAboveShelfStateChanged(false);
513     }
514 
515     @Test
testIconScrollXAfterTranslationAndReset()516     public void testIconScrollXAfterTranslationAndReset() throws Exception {
517         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
518 
519         group.setDismissUsingRowTranslationX(false);
520         group.setTranslation(50);
521         assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX());
522 
523         group.resetTranslation();
524         assertEquals(0, group.getEntry().getIcons().getShelfIcon().getScrollX());
525     }
526 
527     @Test
testIsExpanded_userExpanded()528     public void testIsExpanded_userExpanded() throws Exception {
529         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
530 
531         group.setExpandable(true);
532         Assert.assertFalse(group.isExpanded());
533         group.setUserExpanded(true);
534         Assert.assertTrue(group.isExpanded());
535     }
536 
537     @Test
testGetIsNonblockable()538     public void testGetIsNonblockable() throws Exception {
539         ExpandableNotificationRow row =
540                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
541         row.setEntry(null);
542 
543         assertTrue(row.getIsNonblockable());
544 
545         NotificationEntry entry = mock(NotificationEntry.class);
546 
547         Mockito.doReturn(false, true).when(entry).isBlockable();
548         row.setEntry(entry);
549         assertTrue(row.getIsNonblockable());
550         assertFalse(row.getIsNonblockable());
551     }
552 
553     @Test
testCanDismiss()554     public void testCanDismiss() throws Exception {
555         ExpandableNotificationRow row =
556                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
557         when(mNotificationTestHelper.getDismissibilityProvider().isDismissable(row.getEntry()))
558                 .thenReturn(true);
559         row.performDismiss(false);
560         verify(mNotificationTestHelper.getOnUserInteractionCallback())
561                 .registerFutureDismissal(any(), anyInt());
562     }
563 
564     @Test
testCannotDismiss()565     public void testCannotDismiss() throws Exception {
566         ExpandableNotificationRow row =
567                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
568         when(mNotificationTestHelper.getDismissibilityProvider().isDismissable(row.getEntry()))
569                 .thenReturn(false);
570         row.performDismiss(false);
571         verify(mNotificationTestHelper.getOnUserInteractionCallback(), never())
572                 .registerFutureDismissal(any(), anyInt());
573     }
574 
575     @Test
testAddChildNotification()576     public void testAddChildNotification() throws Exception {
577         ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
578         ExpandableNotificationRow child = mNotificationTestHelper.createRow();
579 
580         group.addChildNotification(child);
581 
582         Assert.assertEquals(child, group.getChildNotificationAt(0));
583         Assert.assertEquals(group, child.getNotificationParent());
584         Assert.assertTrue(child.isChildInGroup());
585     }
586 
587     @Test
testAddChildNotification_childSkipped()588     public void testAddChildNotification_childSkipped() throws Exception {
589         ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
590         ExpandableNotificationRow child = mNotificationTestHelper.createRow();
591         child.setKeepInParentForDismissAnimation(true);
592 
593         group.addChildNotification(child);
594 
595         Assert.assertTrue(group.getAttachedChildren().isEmpty());
596         Assert.assertNotEquals(group, child.getNotificationParent());
597         verify(mNotificationTestHelper.getMockLogger()).logSkipAttachingKeepInParentChild(
598                 /*child=*/ child.getEntry(),
599                 /*newParent=*/ group.getEntry()
600         );
601     }
602 
603     @Test
testRemoveChildNotification()604     public void testRemoveChildNotification() throws Exception {
605         ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
606         ExpandableNotificationRow child = group.getAttachedChildren().get(0);
607         child.setKeepInParentForDismissAnimation(true);
608 
609         group.removeChildNotification(child);
610 
611         Assert.assertNull(child.getParent());
612         Assert.assertNull(child.getNotificationParent());
613         Assert.assertFalse(child.keepInParentForDismissAnimation());
614         verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger());
615     }
616 
617     @Test
testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent()618     public void testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent() throws Exception {
619         ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
620         ExpandableNotificationRow child = group.getAttachedChildren().get(0);
621         child.setKeepInParentForDismissAnimation(true);
622 
623         group.removeChildrenWithKeepInParent();
624 
625         Assert.assertNull(child.getParent());
626         Assert.assertNull(child.getNotificationParent());
627         Assert.assertFalse(child.keepInParentForDismissAnimation());
628         verify(mNotificationTestHelper.getMockLogger()).logKeepInParentChildDetached(
629                 /*child=*/ child.getEntry(),
630                 /*oldParent=*/ group.getEntry()
631         );
632     }
633 
634     @Test
testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent()635     public void testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent()
636             throws Exception {
637         ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
638         ExpandableNotificationRow child = group.getAttachedChildren().get(0);
639 
640         group.removeChildrenWithKeepInParent();
641 
642         Assert.assertEquals(group, child.getNotificationParent());
643         Assert.assertFalse(child.keepInParentForDismissAnimation());
644         verify(mNotificationTestHelper.getMockLogger(), never()).logKeepInParentChildDetached(
645                 /*child=*/ any(),
646                 /*oldParent=*/ any()
647         );
648     }
649 
650     @Test
applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer()651     public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer()
652             throws Exception {
653         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
654         Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f);
655         Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
656 
657         group.requestBottomRoundness(1f, SourceType.from(""), false);
658 
659         Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f);
660         Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
661     }
662 
663     @Test
testSetContentAnimationRunning_Run()664     public void testSetContentAnimationRunning_Run() throws Exception {
665         // Create views for the notification row.
666         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
667         NotificationContentView publicLayout = mock(NotificationContentView.class);
668         row.setPublicLayout(publicLayout);
669         NotificationContentView privateLayout = mock(NotificationContentView.class);
670         row.setPrivateLayout(privateLayout);
671 
672         row.setAnimationRunning(true);
673         verify(publicLayout, times(1)).setContentAnimationRunning(true);
674         verify(privateLayout, times(1)).setContentAnimationRunning(true);
675     }
676 
677     @Test
testSetContentAnimationRunning_Stop()678     public void testSetContentAnimationRunning_Stop() throws Exception {
679         // Create views for the notification row.
680         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
681         NotificationContentView publicLayout = mock(NotificationContentView.class);
682         row.setPublicLayout(publicLayout);
683         NotificationContentView privateLayout = mock(NotificationContentView.class);
684         row.setPrivateLayout(privateLayout);
685 
686         row.setAnimationRunning(false);
687         verify(publicLayout, times(1)).setContentAnimationRunning(false);
688         verify(privateLayout, times(1)).setContentAnimationRunning(false);
689     }
690 
691     @Test
testSetContentAnimationRunningInGroupChild_Run()692     public void testSetContentAnimationRunningInGroupChild_Run() throws Exception {
693         // Creates parent views on groupRow.
694         ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
695         NotificationContentView publicParentLayout = mock(NotificationContentView.class);
696         groupRow.setPublicLayout(publicParentLayout);
697         NotificationContentView privateParentLayout = mock(NotificationContentView.class);
698         groupRow.setPrivateLayout(privateParentLayout);
699 
700         // Create child views on row.
701         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
702         NotificationContentView publicChildLayout = mock(NotificationContentView.class);
703         row.setPublicLayout(publicChildLayout);
704         NotificationContentView privateChildLayout = mock(NotificationContentView.class);
705         row.setPrivateLayout(privateChildLayout);
706         when(row.isGroupExpanded()).thenReturn(true);
707         setMockChildrenContainer(groupRow, row);
708 
709         groupRow.setAnimationRunning(true);
710         verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
711         verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
712         // The child layouts should be started too.
713         verify(publicChildLayout, times(1)).setContentAnimationRunning(true);
714         verify(privateChildLayout, times(1)).setContentAnimationRunning(true);
715     }
716 
717 
718     @Test
testSetIconAnimationRunningGroup_Run()719     public void testSetIconAnimationRunningGroup_Run() throws Exception {
720         // Create views for a group row.
721         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
722         ExpandableNotificationRow child = mNotificationTestHelper.createRow();
723         NotificationContentView publicParentLayout = mock(NotificationContentView.class);
724         group.setPublicLayout(publicParentLayout);
725         NotificationContentView privateParentLayout = mock(NotificationContentView.class);
726         group.setPrivateLayout(privateParentLayout);
727         when(group.isGroupExpanded()).thenReturn(true);
728 
729         // Add the child to the group.
730         NotificationContentView publicChildLayout = mock(NotificationContentView.class);
731         child.setPublicLayout(publicChildLayout);
732         NotificationContentView privateChildLayout = mock(NotificationContentView.class);
733         child.setPrivateLayout(privateChildLayout);
734         when(child.isGroupExpanded()).thenReturn(true);
735 
736         NotificationChildrenContainer mockContainer =
737                 setMockChildrenContainer(group, child);
738 
739         // Mock the children view wrappers, and give them each an icon.
740         NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
741         when(mockContainer.getNotificationViewWrapper()).thenReturn(mockViewWrapper);
742         CachingIconView mockIcon = mock(CachingIconView.class);
743         when(mockViewWrapper.getIcon()).thenReturn(mockIcon);
744 
745         NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class);
746         when(mockContainer.getMinimizedGroupHeaderWrapper()).thenReturn(mockLowPriorityViewWrapper);
747         CachingIconView mockLowPriorityIcon = mock(CachingIconView.class);
748         when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon);
749 
750         // Give the icon image views drawables, so we can make sure they animate.
751         // We use both AnimationDrawables and AnimatedVectorDrawables to ensure both work.
752         AnimationDrawable drawable = mock(AnimationDrawable.class);
753         AnimatedVectorDrawable vectorDrawable = mock(AnimatedVectorDrawable.class);
754         setDrawableIconsInImageView(mockIcon, drawable, vectorDrawable);
755 
756         AnimationDrawable lowPriDrawable = mock(AnimationDrawable.class);
757         AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
758         setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
759 
760         group.setAnimationRunning(true);
761         verify(drawable, times(1)).start();
762         verify(vectorDrawable, times(1)).start();
763         verify(lowPriDrawable, times(1)).start();
764         verify(lowPriVectorDrawable, times(1)).start();
765     }
766 
767     @Test
isExpanded_hideSensitive_sensitiveNotExpanded()768     public void isExpanded_hideSensitive_sensitiveNotExpanded()
769             throws Exception {
770         // GIVEN
771         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
772         row.setUserExpanded(true);
773         row.setOnKeyguard(false);
774         row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
775         row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true);
776 
777         // THEN
778         assertThat(row.isExpanded()).isFalse();
779     }
780 
781     @Test
isExpanded_hideSensitive_nonSensitiveExpanded()782     public void isExpanded_hideSensitive_nonSensitiveExpanded()
783             throws Exception {
784         // GIVEN
785         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
786         row.setUserExpanded(true);
787         row.setOnKeyguard(false);
788         row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
789         row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */false);
790 
791         // THEN
792         assertThat(row.isExpanded()).isTrue();
793     }
794 
795     @Test
onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()796     public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()
797             throws Exception {
798         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
799 
800         // Initial state: suppose heads up animation in progress
801         row.setHeadsUpAnimatingAway(true);
802         assertThat(row.isHeadsUpAnimatingAway()).isTrue();
803 
804         // on disappear animation ends
805         row.onAppearAnimationFinished(/* wasAppearing = */ false);
806         assertThat(row.isHeadsUpAnimatingAway()).isFalse();
807     }
808 
809     @Test
onHUNAppear_cancelAppearDrawing_shouldResetAnimationState()810     public void onHUNAppear_cancelAppearDrawing_shouldResetAnimationState() throws Exception {
811         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
812 
813         row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ true);
814 
815         waitForIdleSync();
816         assertThat(row.isDrawingAppearAnimation()).isTrue();
817 
818         row.cancelAppearDrawing();
819 
820         waitForIdleSync();
821         assertThat(row.isDrawingAppearAnimation()).isFalse();
822     }
823 
824     @Test
onHUNDisappear_cancelAppearDrawing_shouldResetAnimationState()825     public void onHUNDisappear_cancelAppearDrawing_shouldResetAnimationState() throws Exception {
826         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
827 
828         row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ false);
829 
830         waitForIdleSync();
831         assertThat(row.isDrawingAppearAnimation()).isTrue();
832 
833         row.cancelAppearDrawing();
834 
835         waitForIdleSync();
836         assertThat(row.isDrawingAppearAnimation()).isFalse();
837     }
838 
839     @Test
imageResolver_sameNotificationUser_usesContext()840     public void imageResolver_sameNotificationUser_usesContext() throws Exception {
841         ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG,
842                 USER_HANDLE.getUid(1234), USER_HANDLE);
843 
844         assertThat(row.getImageResolver().getContext()).isSameInstanceAs(mContext);
845     }
846 
847     @Test
imageResolver_differentNotificationUser_createsUserContext()848     public void imageResolver_differentNotificationUser_createsUserContext() throws Exception {
849         UserHandle user = new UserHandle(33);
850         Context userContext = new SysuiTestableContext(mContext);
851         mContext.prepareCreateContextAsUser(user, userContext);
852 
853         ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG,
854                 user.getUid(1234), user);
855 
856         assertThat(row.getImageResolver().getContext()).isSameInstanceAs(userContext);
857     }
858 
setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable, Drawable rightIconDrawable)859     private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
860             Drawable rightIconDrawable) {
861         ImageView iconView = mock(ImageView.class);
862         when(icon.findViewById(com.android.internal.R.id.icon)).thenReturn(iconView);
863         when(iconView.getDrawable()).thenReturn(iconDrawable);
864 
865         ImageView rightIconView = mock(ImageView.class);
866         when(icon.findViewById(com.android.internal.R.id.right_icon)).thenReturn(rightIconView);
867         when(rightIconView.getDrawable()).thenReturn(rightIconDrawable);
868     }
869 
setMockChildrenContainer( ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow)870     private NotificationChildrenContainer setMockChildrenContainer(
871             ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow) {
872         List<ExpandableNotificationRow> rowList = Arrays.asList(childRow);
873         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
874         when(mockContainer.getNotificationChildCount()).thenReturn(1);
875         when(mockContainer.getAttachedChildren()).thenReturn(rowList);
876         parentRow.setChildrenContainer(mockContainer);
877         return mockContainer;
878     }
879 }
880