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