1 /* 2 * Copyright (C) 2019 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.stack; 18 19 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.Mockito.RETURNS_DEEP_STUBS; 25 import static org.mockito.Mockito.clearInvocations; 26 import static org.mockito.Mockito.mock; 27 import static org.mockito.Mockito.never; 28 import static org.mockito.Mockito.verify; 29 import static org.mockito.Mockito.when; 30 31 import android.testing.AndroidTestingRunner; 32 import android.testing.TestableLooper; 33 import android.util.AttributeSet; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 38 import androidx.test.filters.SmallTest; 39 40 import com.android.systemui.ActivityStarterDelegate; 41 import com.android.systemui.SysuiTestCase; 42 import com.android.systemui.plugins.statusbar.StatusBarStateController; 43 import com.android.systemui.statusbar.StatusBarState; 44 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 45 import com.android.systemui.statusbar.policy.ConfigurationController; 46 47 import org.junit.Before; 48 import org.junit.Rule; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 import org.mockito.Mock; 52 import org.mockito.junit.MockitoJUnit; 53 import org.mockito.junit.MockitoRule; 54 55 @SmallTest 56 @RunWith(AndroidTestingRunner.class) 57 @TestableLooper.RunWithLooper 58 public class NotificationSectionsManagerTest extends SysuiTestCase { 59 60 @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); 61 62 @Mock private NotificationStackScrollLayout mNssl; 63 @Mock private ActivityStarterDelegate mActivityStarterDelegate; 64 @Mock private StatusBarStateController mStatusBarStateController; 65 @Mock private ConfigurationController mConfigurationController; 66 67 private NotificationSectionsManager mSectionsManager; 68 69 @Before setUp()70 public void setUp() { 71 mSectionsManager = 72 new NotificationSectionsManager( 73 mNssl, 74 mActivityStarterDelegate, 75 mStatusBarStateController, 76 mConfigurationController, 77 true); 78 // Required in order for the header inflation to work properly 79 when(mNssl.generateLayoutParams(any(AttributeSet.class))) 80 .thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); 81 mSectionsManager.initialize(LayoutInflater.from(mContext)); 82 when(mNssl.indexOfChild(any(View.class))).thenReturn(-1); 83 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); 84 } 85 86 @Test(expected = IllegalStateException.class) testDuplicateInitializeThrows()87 public void testDuplicateInitializeThrows() { 88 mSectionsManager.initialize(LayoutInflater.from(mContext)); 89 } 90 91 @Test testInsertHeader()92 public void testInsertHeader() { 93 // GIVEN a stack with HI and LO rows but no section headers 94 setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI); 95 96 // WHEN we update the section headers 97 mSectionsManager.updateSectionBoundaries(); 98 99 // THEN a LO section header is added 100 verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3); 101 } 102 103 @Test testRemoveHeader()104 public void testRemoveHeader() { 105 // GIVEN a stack that originally had a header between the HI and LO sections 106 setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI); 107 mSectionsManager.updateSectionBoundaries(); 108 109 // WHEN the last LO row is replaced with a HI row 110 setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HEADER, ChildType.HIPRI); 111 clearInvocations(mNssl); 112 mSectionsManager.updateSectionBoundaries(); 113 114 // THEN the LO section header is removed 115 verify(mNssl).removeView(mSectionsManager.getGentleHeaderView()); 116 } 117 118 @Test testDoNothingIfHeaderAlreadyRemoved()119 public void testDoNothingIfHeaderAlreadyRemoved() { 120 // GIVEN a stack with only HI rows 121 setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI); 122 123 // WHEN we update the sections headers 124 mSectionsManager.updateSectionBoundaries(); 125 126 // THEN we don't add any section headers 127 verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt()); 128 } 129 130 @Test testMoveHeaderForward()131 public void testMoveHeaderForward() { 132 // GIVEN a stack that originally had a header between the HI and LO sections 133 setStackState( 134 ChildType.HIPRI, 135 ChildType.HIPRI, 136 ChildType.HIPRI, 137 ChildType.LOPRI); 138 mSectionsManager.updateSectionBoundaries(); 139 140 // WHEN the LO section moves forward 141 setStackState( 142 ChildType.HIPRI, 143 ChildType.HIPRI, 144 ChildType.LOPRI, 145 ChildType.HEADER, 146 ChildType.LOPRI); 147 mSectionsManager.updateSectionBoundaries(); 148 149 // THEN the LO section header is also moved forward 150 verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2); 151 } 152 153 @Test testMoveHeaderBackward()154 public void testMoveHeaderBackward() { 155 // GIVEN a stack that originally had a header between the HI and LO sections 156 setStackState( 157 ChildType.HIPRI, 158 ChildType.LOPRI, 159 ChildType.LOPRI, 160 ChildType.LOPRI); 161 mSectionsManager.updateSectionBoundaries(); 162 163 // WHEN the LO section moves backward 164 setStackState( 165 ChildType.HIPRI, 166 ChildType.HEADER, 167 ChildType.HIPRI, 168 ChildType.HIPRI, 169 ChildType.LOPRI); 170 mSectionsManager.updateSectionBoundaries(); 171 172 // THEN the LO section header is also moved backward (with appropriate index shifting) 173 verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 3); 174 } 175 176 @Test testHeaderRemovedFromTransientParent()177 public void testHeaderRemovedFromTransientParent() { 178 // GIVEN a stack where the header is animating away 179 setStackState( 180 ChildType.HIPRI, 181 ChildType.LOPRI, 182 ChildType.LOPRI, 183 ChildType.LOPRI); 184 mSectionsManager.updateSectionBoundaries(); 185 setStackState( 186 ChildType.HIPRI, 187 ChildType.HEADER); 188 mSectionsManager.updateSectionBoundaries(); 189 clearInvocations(mNssl); 190 191 ViewGroup transientParent = mock(ViewGroup.class); 192 mSectionsManager.getGentleHeaderView().setTransientContainer(transientParent); 193 194 // WHEN the LO section reappears 195 setStackState( 196 ChildType.HIPRI, 197 ChildType.LOPRI); 198 mSectionsManager.updateSectionBoundaries(); 199 200 // THEN the header is first removed from the transient parent before being added to the 201 // NSSL. 202 verify(transientParent).removeTransientView(mSectionsManager.getGentleHeaderView()); 203 verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 1); 204 } 205 206 @Test testHeaderNotShownOnLockscreen()207 public void testHeaderNotShownOnLockscreen() { 208 // GIVEN a stack of HI and LO notifs on the lockscreen 209 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 210 setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI); 211 212 // WHEN we update the section headers 213 mSectionsManager.updateSectionBoundaries(); 214 215 // Then the section header is not added 216 verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt()); 217 } 218 219 @Test testHeaderShownWhenEnterLockscreen()220 public void testHeaderShownWhenEnterLockscreen() { 221 // GIVEN a stack of HI and LO notifs on the lockscreen 222 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 223 setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI); 224 mSectionsManager.updateSectionBoundaries(); 225 226 // WHEN we unlock 227 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); 228 mSectionsManager.updateSectionBoundaries(); 229 230 // Then the section header is added 231 verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3); 232 } 233 234 @Test testHeaderHiddenWhenEnterLockscreen()235 public void testHeaderHiddenWhenEnterLockscreen() { 236 // GIVEN a stack of HI and LO notifs on the shade 237 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); 238 setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI); 239 mSectionsManager.updateSectionBoundaries(); 240 241 // WHEN we go back to the keyguard 242 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 243 mSectionsManager.updateSectionBoundaries(); 244 245 // Then the section header is removed 246 verify(mNssl).removeView(eq(mSectionsManager.getGentleHeaderView())); 247 } 248 249 private enum ChildType { HEADER, HIPRI, LOPRI } 250 setStackState(ChildType... children)251 private void setStackState(ChildType... children) { 252 when(mNssl.getChildCount()).thenReturn(children.length); 253 for (int i = 0; i < children.length; i++) { 254 View child; 255 switch (children[i]) { 256 case HEADER: 257 child = mSectionsManager.getGentleHeaderView(); 258 break; 259 case HIPRI: 260 case LOPRI: 261 ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class, 262 RETURNS_DEEP_STUBS); 263 when(notifRow.getVisibility()).thenReturn(View.VISIBLE); 264 when(notifRow.getEntry().isHighPriority()) 265 .thenReturn(children[i] == ChildType.HIPRI); 266 when(notifRow.getEntry().isTopBucket()) 267 .thenReturn(children[i] == ChildType.HIPRI); 268 when(notifRow.getParent()).thenReturn(mNssl); 269 child = notifRow; 270 break; 271 default: 272 throw new RuntimeException("Unknown ChildType: " + children[i]); 273 } 274 when(mNssl.getChildAt(i)).thenReturn(child); 275 when(mNssl.indexOfChild(child)).thenReturn(i); 276 } 277 } 278 } 279