1 /*
2  * Copyright (C) 2023 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.qs;
18 
19 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
20 
21 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
22 import static com.android.systemui.statusbar.StatusBarState.SHADE;
23 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertTrue;
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.ArgumentMatchers.anyBoolean;
31 import static org.mockito.ArgumentMatchers.anyInt;
32 import static org.mockito.ArgumentMatchers.eq;
33 import static org.mockito.ArgumentMatchers.nullable;
34 import static org.mockito.Mockito.clearInvocations;
35 import static org.mockito.Mockito.doAnswer;
36 import static org.mockito.Mockito.mock;
37 import static org.mockito.Mockito.spy;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.verifyZeroInteractions;
40 import static org.mockito.Mockito.when;
41 
42 import android.content.Context;
43 import android.graphics.Rect;
44 import android.os.Bundle;
45 import android.testing.TestableLooper.RunWithLooper;
46 import android.view.Display;
47 import android.view.LayoutInflater;
48 import android.view.View;
49 import android.view.ViewGroup;
50 import android.widget.FrameLayout;
51 
52 import androidx.compose.ui.platform.ComposeView;
53 import androidx.lifecycle.Lifecycle;
54 import androidx.test.ext.junit.runners.AndroidJUnit4;
55 import androidx.test.filters.SmallTest;
56 
57 import com.android.keyguard.BouncerPanelExpansionCalculator;
58 import com.android.systemui.SysuiTestCase;
59 import com.android.systemui.dump.DumpManager;
60 import com.android.systemui.flags.EnableSceneContainer;
61 import com.android.systemui.media.controls.ui.view.MediaHost;
62 import com.android.systemui.qs.customize.QSCustomizerController;
63 import com.android.systemui.qs.dagger.QSComponent;
64 import com.android.systemui.qs.external.TileServiceRequestController;
65 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
66 import com.android.systemui.qs.logging.QSLogger;
67 import com.android.systemui.res.R;
68 import com.android.systemui.settings.FakeDisplayTracker;
69 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
70 import com.android.systemui.statusbar.CommandQueue;
71 import com.android.systemui.statusbar.StatusBarState;
72 import com.android.systemui.statusbar.SysuiStatusBarStateController;
73 import com.android.systemui.statusbar.phone.KeyguardBypassController;
74 import com.android.systemui.statusbar.policy.ConfigurationController;
75 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
76 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
77 import com.android.systemui.util.animation.UniqueObjectHostView;
78 
79 import org.junit.Before;
80 import org.junit.Test;
81 import org.junit.runner.RunWith;
82 import org.mockito.Mock;
83 import org.mockito.MockitoAnnotations;
84 
85 @RunWith(AndroidJUnit4.class)
86 @RunWithLooper(setAsMainLooper = true)
87 @SmallTest
88 public class QSImplTest extends SysuiTestCase {
89 
90     @Mock private QSComponent mQsComponent;
91     @Mock private QSPanelController mQSPanelController;
92     @Mock private MediaHost mQSMediaHost;
93     @Mock private MediaHost mQQSMediaHost;
94     @Mock private KeyguardBypassController mBypassController;
95     @Mock private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
96     @Mock private TileServiceRequestController mTileServiceRequestController;
97     @Mock private QSCustomizerController mQsCustomizerController;
98     @Mock private QuickQSPanelController mQuickQSPanelController;
99     @Mock private FooterActionsController mQSFooterActionController;
100     @Mock private QSContainerImplController mQSContainerImplController;
101     @Mock private QSContainerImpl mContainer;
102     @Mock private QSFooter mFooter;
103     @Mock private LayoutInflater mLayoutInflater;
104     @Mock private NonInterceptingScrollView mQSPanelScrollView;
105     @Mock private QuickStatusBarHeader mHeader;
106     @Mock private QSPanel.QSTileLayout mQsTileLayout;
107     @Mock private QSPanel.QSTileLayout mQQsTileLayout;
108     @Mock private QSAnimator mQSAnimator;
109     @Mock private SysuiStatusBarStateController mStatusBarStateController;
110     @Mock private QSSquishinessController mSquishinessController;
111     @Mock private FooterActionsViewModel mFooterActionsViewModel;
112     @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
113     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
114     private ViewGroup mQsView;
115 
116     private final CommandQueue mCommandQueue =
117             new CommandQueue(mContext, new FakeDisplayTracker(mContext));
118 
119     private QSImpl mUnderTest;
120 
121 
122     @Before
setup()123     public void setup() {
124         MockitoAnnotations.initMocks(this);
125 
126         mUnderTest = instantiate();
127 
128         mUnderTest.onComponentCreated(mQsComponent, null);
129     }
130 
131     /*
132      * Regression test for b/303180152.
133      */
134     @Test
testDisableCallbackOnDisabledQuickSettingsUponCreationDoesntCrash()135     public void testDisableCallbackOnDisabledQuickSettingsUponCreationDoesntCrash() {
136         QSImpl other = instantiate();
137         mCommandQueue.disable(Display.DEFAULT_DISPLAY, 0, DISABLE2_QUICK_SETTINGS);
138 
139         other.onComponentCreated(mQsComponent, null);
140     }
141 
142     @Test
testSaveState()143     public void testSaveState() {
144         mUnderTest.setListening(true);
145         mUnderTest.setExpanded(true);
146         mUnderTest.setQsVisible(true);
147 
148         Bundle bundle = new Bundle();
149         mUnderTest.onSaveInstanceState(bundle);
150 
151         // Get a new instance
152         QSImpl other = instantiate();
153         other.onComponentCreated(mQsComponent, bundle);
154 
155         assertTrue(other.isListening());
156         assertTrue(other.isExpanded());
157         assertTrue(other.isQsVisible());
158     }
159 
160     @Test
transitionToFullShade_smallScreen_alphaAlways1()161     public void transitionToFullShade_smallScreen_alphaAlways1() {
162         setIsSmallScreen();
163         setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
164         boolean isTransitioningToFullShade = true;
165         float transitionProgress = 0.5f;
166         float squishinessFraction = 0.5f;
167 
168         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
169                 squishinessFraction);
170 
171         assertThat(mQsView.getAlpha()).isEqualTo(1f);
172     }
173 
174     @Test
transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator()175     public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() {
176         setIsLargeScreen();
177         setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
178         boolean isTransitioningToFullShade = true;
179         float transitionProgress = 0.5f;
180         float squishinessFraction = 0.5f;
181         when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
182 
183         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
184                 squishinessFraction);
185 
186         assertThat(mQsView.getAlpha()).isEqualTo(123f);
187     }
188 
189     @Test
190     public void
transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator()191             transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
192         setStatusBarCurrentAndUpcomingState(KEYGUARD);
193         when(mQSPanelController.isBouncerInTransit()).thenReturn(false);
194         boolean isTransitioningToFullShade = true;
195         float transitionProgress = 0.5f;
196         float squishinessFraction = 0.5f;
197 
198         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
199                 squishinessFraction);
200 
201         assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress);
202     }
203 
204     @Test
205     public void
transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator()206             transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() {
207         setStatusBarCurrentAndUpcomingState(KEYGUARD);
208         when(mQSPanelController.isBouncerInTransit()).thenReturn(true);
209         boolean isTransitioningToFullShade = true;
210         float transitionProgress = 0.5f;
211         float squishinessFraction = 0.5f;
212 
213         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
214                 squishinessFraction);
215 
216         assertThat(mQsView.getAlpha())
217                 .isEqualTo(
218                         BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
219                                 transitionProgress));
220     }
221 
222     @Test
transitionToFullShade_inFullWidth_alwaysSetsAlphaTo1()223     public void transitionToFullShade_inFullWidth_alwaysSetsAlphaTo1() {
224         mUnderTest.setIsNotificationPanelFullWidth(true);
225 
226         boolean isTransitioningToFullShade = true;
227         float transitionProgress = 0.1f;
228         float squishinessFraction = 0.5f;
229         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
230                 squishinessFraction);
231         assertThat(mQsView.getAlpha()).isEqualTo(1);
232 
233         transitionProgress = 0.5f;
234         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
235                 squishinessFraction);
236         assertThat(mQsView.getAlpha()).isEqualTo(1);
237         assertThat(mQsView.getAlpha()).isEqualTo(1);
238 
239         transitionProgress = 0.7f;
240         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
241                 squishinessFraction);
242         assertThat(mQsView.getAlpha()).isEqualTo(1);
243     }
244 
245     @Test
transitionToFullShade_setsSquishinessOnController()246     public void transitionToFullShade_setsSquishinessOnController() {
247         boolean isTransitioningToFullShade = true;
248         float transitionProgress = 0.123f;
249         float squishinessFraction = 0.456f;
250 
251         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
252                 squishinessFraction);
253 
254         verify(mQsComponent.getQSSquishinessController()).setSquishiness(squishinessFraction);
255     }
256 
257     @Test
setQsExpansion_whenShouldUpdateSquishinessTrue_setsSquishinessBasedOnFraction()258     public void setQsExpansion_whenShouldUpdateSquishinessTrue_setsSquishinessBasedOnFraction() {
259         enableSplitShade();
260         when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
261         float expansion = 0.456f;
262         float panelExpansionFraction = 0.678f;
263         float proposedTranslation = 567f;
264         float squishinessFraction = 0.789f;
265 
266         mUnderTest.setShouldUpdateSquishinessOnMedia(true);
267         mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
268                 squishinessFraction);
269 
270         verify(mQSMediaHost).setSquishFraction(squishinessFraction);
271     }
272 
273     @Test
setQsExpansion_whenOnKeyguardAndShouldUpdateSquishinessFalse_setsSquishiness()274     public void setQsExpansion_whenOnKeyguardAndShouldUpdateSquishinessFalse_setsSquishiness() {
275         // Random test values without any meaning. They just have to be different from each other.
276         float expansion = 0.123f;
277         float panelExpansionFraction = 0.321f;
278         float proposedTranslation = 456f;
279         float squishinessFraction = 0.567f;
280 
281         enableSplitShade();
282         setStatusBarCurrentAndUpcomingState(KEYGUARD);
283         mUnderTest.setShouldUpdateSquishinessOnMedia(false);
284         mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
285                 squishinessFraction);
286 
287         verify(mQSMediaHost).setSquishFraction(1.0f);
288     }
289 
290     @Test
setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction()291     public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() {
292         // Random test values without any meaning. They just have to be different from each other.
293         float expansion = 0.123f;
294         float panelExpansionFraction = 0.321f;
295         float proposedTranslation = 456f;
296         float squishinessFraction = 0.987f;
297 
298         enableSplitShade();
299 
300         mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
301                 squishinessFraction);
302 
303         verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
304                 panelExpansionFraction, /* isInSplitShade= */ true);
305     }
306 
307     @Test
setQsExpansion_notInSplitShade_setsFooterActionsExpansion_basedOnExpansion()308     public void setQsExpansion_notInSplitShade_setsFooterActionsExpansion_basedOnExpansion() {
309         // Random test values without any meaning. They just have to be different from each other.
310         float expansion = 0.123f;
311         float panelExpansionFraction = 0.321f;
312         float proposedTranslation = 456f;
313         float squishinessFraction = 0.987f;
314 
315         disableSplitShade();
316 
317         mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
318                 squishinessFraction);
319 
320         verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
321                 expansion, /* isInSplitShade= */ false);
322     }
323 
324     @Test
setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress()325     public void setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress() {
326         enableSplitShade();
327         when(mStatusBarStateController.getState()).thenReturn(SHADE);
328         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
329         boolean isTransitioningToFullShade = false;
330         float transitionProgress = 0;
331         float squishinessFraction = 0f;
332 
333         mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
334                 squishinessFraction);
335 
336         // trigger alpha refresh with non-zero expansion and fraction values
337         mUnderTest.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
338                 /* proposedTranslation= */ 0, /* squishinessFraction= */ 1);
339 
340         // alpha should follow lockscreen to shade progress, not panel expansion fraction
341         assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress);
342     }
343 
344     @Test
getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight()345     public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
346         disableSplitShade();
347         when(mHeader.getHeight()).thenReturn(1234);
348 
349         int height = mUnderTest.getQsMinExpansionHeight();
350 
351         assertThat(height).isEqualTo(mHeader.getHeight());
352     }
353 
354     @Test
getQsMinExpansionHeight_inSplitShade_returnsAbsoluteBottomOfQSContainer()355     public void getQsMinExpansionHeight_inSplitShade_returnsAbsoluteBottomOfQSContainer() {
356         int top = 1234;
357         int height = 9876;
358         enableSplitShade();
359         setLocationOnScreen(mQsView, top);
360         when(mQsView.getHeight()).thenReturn(height);
361 
362         int expectedHeight = top + height;
363         assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
364     }
365 
366     @Test
getQsMinExpansionHeight_inSplitShade_returnsAbsoluteBottomExcludingTranslation()367     public void getQsMinExpansionHeight_inSplitShade_returnsAbsoluteBottomExcludingTranslation() {
368         int top = 1234;
369         int height = 9876;
370         float translationY = -600f;
371         enableSplitShade();
372         setLocationOnScreen(mQsView, (int) (top + translationY));
373         when(mQsView.getHeight()).thenReturn(height);
374         when(mQsView.getTranslationY()).thenReturn(translationY);
375 
376         int expectedHeight = top + height;
377         assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
378     }
379 
380     @Test
hideImmediately_notInSplitShade_movesViewUpByHeaderHeight()381     public void hideImmediately_notInSplitShade_movesViewUpByHeaderHeight() {
382         disableSplitShade();
383         when(mHeader.getHeight()).thenReturn(555);
384 
385         mUnderTest.hideImmediately();
386 
387         assertThat(mQsView.getY()).isEqualTo(-mHeader.getHeight());
388     }
389 
390     @Test
hideImmediately_inSplitShade_movesViewUpByQSAbsoluteBottom()391     public void hideImmediately_inSplitShade_movesViewUpByQSAbsoluteBottom() {
392         enableSplitShade();
393         int top = 1234;
394         int height = 9876;
395         setLocationOnScreen(mQsView, top);
396         when(mQsView.getHeight()).thenReturn(height);
397 
398         mUnderTest.hideImmediately();
399 
400         int qsAbsoluteBottom = top + height;
401         assertThat(mQsView.getY()).isEqualTo(-qsAbsoluteBottom);
402     }
403 
404     @Test
setCollapseExpandAction_passedToControllers()405     public void setCollapseExpandAction_passedToControllers() {
406         Runnable action = () -> {};
407         mUnderTest.setCollapseExpandAction(action);
408 
409         verify(mQSPanelController).setCollapseExpandAction(action);
410         verify(mQuickQSPanelController).setCollapseExpandAction(action);
411     }
412 
413     @Test
setOverScrollAmount_setsTranslationOnView()414     public void setOverScrollAmount_setsTranslationOnView() {
415         mUnderTest.setOverScrollAmount(123);
416 
417         assertThat(mQsView.getTranslationY()).isEqualTo(123);
418     }
419 
420     @Test
setOverScrollAmount_beforeViewCreated_translationIsNotSet()421     public void setOverScrollAmount_beforeViewCreated_translationIsNotSet() {
422         QSImpl other = instantiate();
423         other.setOverScrollAmount(123);
424 
425         assertThat(mQsView.getTranslationY()).isEqualTo(0);
426     }
427 
getListeningAndVisibilityLifecycleState()428     private Lifecycle.State getListeningAndVisibilityLifecycleState() {
429         return mUnderTest
430                 .getListeningAndVisibilityLifecycleOwner()
431                 .getLifecycle()
432                 .getCurrentState();
433     }
434 
435     @Test
setListeningFalse_notVisible()436     public void setListeningFalse_notVisible() {
437         mUnderTest.setQsVisible(false);
438         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
439 
440         mUnderTest.setListening(false);
441         verify(mQSContainerImplController).setListening(false);
442         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
443         verify(mQSPanelController).setListening(eq(false), anyBoolean());
444     }
445 
446     @Test
setListeningTrue_notVisible()447     public void setListeningTrue_notVisible() {
448         mUnderTest.setQsVisible(false);
449         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
450 
451         mUnderTest.setListening(true);
452         verify(mQSContainerImplController).setListening(false);
453         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED);
454         verify(mQSPanelController).setListening(eq(false), anyBoolean());
455     }
456 
457     @Test
setListeningFalse_visible()458     public void setListeningFalse_visible() {
459         mUnderTest.setQsVisible(true);
460         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
461 
462         mUnderTest.setListening(false);
463         verify(mQSContainerImplController).setListening(false);
464         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
465         verify(mQSPanelController).setListening(eq(false), anyBoolean());
466     }
467 
468     @Test
setListeningTrue_visible()469     public void setListeningTrue_visible() {
470         mUnderTest.setQsVisible(true);
471         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
472 
473         mUnderTest.setListening(true);
474         verify(mQSContainerImplController).setListening(true);
475         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED);
476         verify(mQSPanelController).setListening(eq(true), anyBoolean());
477     }
478 
479     @Test
passCorrectExpansionState_inSplitShade()480     public void passCorrectExpansionState_inSplitShade() {
481         enableSplitShade();
482         clearInvocations(mQSPanelController);
483 
484         mUnderTest.setExpanded(true);
485         verify(mQSPanelController).setExpanded(true);
486 
487         mUnderTest.setExpanded(false);
488         verify(mQSPanelController).setExpanded(false);
489     }
490 
491     @Test
startsListeningAfterStateChangeToExpanded_inSplitShade()492     public void startsListeningAfterStateChangeToExpanded_inSplitShade() {
493         enableSplitShade();
494         mUnderTest.setQsVisible(true);
495         clearInvocations(mQSPanelController);
496 
497         mUnderTest.setExpanded(true);
498         verify(mQSPanelController).setListening(true, true);
499     }
500 
501     @Test
testUpdateQSBounds_setMediaClipCorrectly()502     public void testUpdateQSBounds_setMediaClipCorrectly() {
503         disableSplitShade();
504 
505         Rect mediaHostClip = new Rect();
506         when(mQSPanelController.getPaddingBottom()).thenReturn(50);
507         setLocationOnScreen(mQSPanelScrollView, 25);
508         when(mQSPanelScrollView.getMeasuredHeight()).thenReturn(200);
509         when(mQSMediaHost.getCurrentClipping()).thenReturn(mediaHostClip);
510 
511         mUnderTest.updateQsBounds();
512 
513         assertEquals(25, mediaHostClip.top);
514         assertEquals(175, mediaHostClip.bottom);
515     }
516 
517     @Test
testQsUpdatesQsAnimatorWithUpcomingState()518     public void testQsUpdatesQsAnimatorWithUpcomingState() {
519         setStatusBarCurrentAndUpcomingState(SHADE);
520         mUnderTest.onUpcomingStateChanged(KEYGUARD);
521 
522         verify(mQSAnimator).setOnKeyguard(true);
523     }
524 
525     @Test
526     @EnableSceneContainer
testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted()527     public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
528         clearInvocations(mFooterActionsViewModel, mFooterActionsViewModelFactory);
529         QSImpl other = instantiate();
530 
531         other.onComponentCreated(mQsComponent, null);
532 
533         assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull();
534         verifyZeroInteractions(mFooterActionsViewModel, mFooterActionsViewModelFactory);
535     }
536 
537     @Test
538     @EnableSceneContainer
testSceneContainerFlagsEnabled_statusBarStateIsShade()539     public void testSceneContainerFlagsEnabled_statusBarStateIsShade() {
540         mUnderTest.onStateChanged(KEYGUARD);
541         assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
542 
543         mUnderTest.onStateChanged(SHADE_LOCKED);
544         assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
545     }
546 
547     @Test
548     @EnableSceneContainer
testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse()549     public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() {
550         mUnderTest.onStateChanged(KEYGUARD);
551         assertThat(mUnderTest.isKeyguardState()).isFalse();
552 
553         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
554         assertThat(mUnderTest.isKeyguardState()).isFalse();
555     }
556 
instantiate()557     private QSImpl instantiate() {
558         setupQsComponent();
559         setUpViews();
560         setUpInflater();
561         setUpMedia();
562         setUpOther();
563 
564         return new QSImpl(
565                 new RemoteInputQuickSettingsDisabler(
566                         mContext,
567                         mCommandQueue,
568                         new ResourcesSplitShadeStateController(),
569                         mock(ConfigurationController.class)),
570                 mStatusBarStateController,
571                 mCommandQueue,
572                 mQSMediaHost,
573                 mQQSMediaHost,
574                 mBypassController,
575                 mock(QSDisableFlagsLogger.class),
576                 mock(DumpManager.class),
577                 mock(QSLogger.class),
578                 mock(FooterActionsController.class),
579                 mFooterActionsViewModelFactory,
580                 mLargeScreenShadeInterpolator
581         );
582     }
583 
setUpOther()584     private void setUpOther() {
585         when(mTileServiceRequestControllerBuilder.create(any()))
586                 .thenReturn(mTileServiceRequestController);
587         when(mQSContainerImplController.getView()).thenReturn(mContainer);
588         when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout);
589         when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout);
590         when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel);
591     }
592 
setUpMedia()593     private void setUpMedia() {
594         when(mQSMediaHost.getCurrentClipping()).thenReturn(new Rect());
595         when(mQSMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(mContext));
596         when(mQQSMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(mContext));
597     }
598 
setUpViews()599     private void setUpViews() {
600         mQsView = spy(new FrameLayout(mContext));
601         when(mQsComponent.getRootView()).thenReturn(mQsView);
602 
603         when(mQSPanelScrollView.findViewById(R.id.expanded_qs_scroll_view))
604                 .thenReturn(mQSPanelScrollView);
605         mQsView.addView(mQSPanelScrollView);
606 
607         when(mHeader.findViewById(R.id.header)).thenReturn(mHeader);
608         mQsView.addView(mHeader);
609 
610         View customizer = new View(mContext);
611         customizer.setId(android.R.id.edit);
612         mQsView.addView(customizer);
613 
614         ComposeView footerActionsView = new ComposeView(mContext);
615         footerActionsView.setId(R.id.qs_footer_actions);
616         mQsView.addView(footerActionsView);
617     }
618 
setUpInflater()619     private void setUpInflater() {
620         when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater);
621         when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean()))
622                 .thenAnswer((invocation) -> mQsView);
623         when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class)))
624                 .thenAnswer((invocation) -> mQsView);
625         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater);
626     }
627 
setupQsComponent()628     private void setupQsComponent() {
629         when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController);
630         when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController);
631         when(mQsComponent.getQSCustomizerController()).thenReturn(mQsCustomizerController);
632         when(mQsComponent.getQSContainerImplController())
633                 .thenReturn(mQSContainerImplController);
634         when(mQsComponent.getQSFooter()).thenReturn(mFooter);
635         when(mQsComponent.getQSFooterActionController())
636                 .thenReturn(mQSFooterActionController);
637         when(mQsComponent.getQSAnimator()).thenReturn(mQSAnimator);
638         when(mQsComponent.getQSSquishinessController()).thenReturn(mSquishinessController);
639     }
640 
setStatusBarCurrentAndUpcomingState(int statusBarState)641     private void setStatusBarCurrentAndUpcomingState(int statusBarState) {
642         when(mStatusBarStateController.getState()).thenReturn(statusBarState);
643         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(statusBarState);
644         mUnderTest.onStateChanged(statusBarState);
645     }
646 
enableSplitShade()647     private void enableSplitShade() {
648         setSplitShadeEnabled(true);
649     }
650 
disableSplitShade()651     private void disableSplitShade() {
652         setSplitShadeEnabled(false);
653     }
654 
setSplitShadeEnabled(boolean enabled)655     private void setSplitShadeEnabled(boolean enabled) {
656         mUnderTest.setInSplitShade(enabled);
657     }
658 
setLocationOnScreen(View view, int top)659     private void setLocationOnScreen(View view, int top) {
660         doAnswer(invocation -> {
661             int[] locationOnScreen = invocation.getArgument(/* index= */ 0);
662             locationOnScreen[0] = 0;
663             locationOnScreen[1] = top;
664             return null;
665         }).when(view).getLocationOnScreen(any(int[].class));
666     }
667 
setIsLargeScreen()668     private void setIsLargeScreen() {
669         mUnderTest.setIsNotificationPanelFullWidth(false);
670     }
671 
setIsSmallScreen()672     private void setIsSmallScreen() {
673         mUnderTest.setIsNotificationPanelFullWidth(true);
674     }
675 }
676