1 /*
2  * Copyright (C) 2018 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.shade;
18 
19 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
20 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
21 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
22 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
23 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
24 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
25 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
26 import static com.android.systemui.statusbar.StatusBarState.SHADE;
27 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
28 
29 import static com.google.common.truth.Truth.assertThat;
30 
31 import static org.mockito.ArgumentMatchers.any;
32 import static org.mockito.ArgumentMatchers.anyBoolean;
33 import static org.mockito.ArgumentMatchers.anyFloat;
34 import static org.mockito.ArgumentMatchers.anyInt;
35 import static org.mockito.ArgumentMatchers.anyString;
36 import static org.mockito.ArgumentMatchers.eq;
37 import static org.mockito.Mockito.atLeastOnce;
38 import static org.mockito.Mockito.clearInvocations;
39 import static org.mockito.Mockito.inOrder;
40 import static org.mockito.Mockito.mock;
41 import static org.mockito.Mockito.never;
42 import static org.mockito.Mockito.reset;
43 import static org.mockito.Mockito.times;
44 import static org.mockito.Mockito.verify;
45 import static org.mockito.Mockito.when;
46 
47 import android.animation.Animator;
48 import android.animation.ValueAnimator;
49 import android.graphics.Point;
50 import android.testing.AndroidTestingRunner;
51 import android.testing.TestableLooper;
52 import android.view.MotionEvent;
53 import android.view.View;
54 import android.view.accessibility.AccessibilityNodeInfo;
55 
56 import androidx.constraintlayout.widget.ConstraintSet;
57 import androidx.test.filters.SmallTest;
58 
59 import com.android.keyguard.FaceAuthApiRequestReason;
60 import com.android.systemui.DejankUtils;
61 import com.android.systemui.R;
62 import com.android.systemui.keyguard.shared.model.WakeSleepReason;
63 import com.android.systemui.keyguard.shared.model.WakefulnessModel;
64 import com.android.systemui.keyguard.shared.model.WakefulnessState;
65 import com.android.systemui.plugins.statusbar.StatusBarStateController;
66 import com.android.systemui.statusbar.notification.row.ExpandableView;
67 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
68 import com.android.systemui.statusbar.notification.stack.AmbientState;
69 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
70 import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
71 
72 import org.junit.Before;
73 import org.junit.Ignore;
74 import org.junit.Test;
75 import org.junit.runner.RunWith;
76 import org.mockito.ArgumentCaptor;
77 import org.mockito.InOrder;
78 
79 import java.util.List;
80 
81 @SmallTest
82 @RunWith(AndroidTestingRunner.class)
83 @TestableLooper.RunWithLooper(setAsMainLooper = true)
84 public class NotificationPanelViewControllerTest extends NotificationPanelViewControllerBaseTest {
85 
86     @Before
before()87     public void before() {
88         DejankUtils.setImmediate(true);
89     }
90 
91     /**
92      * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f).
93      */
94     @Test
testBackGesture_min_scrimAtMaxScale()95     public void testBackGesture_min_scrimAtMaxScale() {
96         mNotificationPanelViewController.onBackProgressed(0.0f);
97         verify(mScrimController).applyBackScaling(1.0f);
98     }
99 
100     /**
101      * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum.
102      */
103     @Test
testBackGesture_max_scrimAtMinScale()104     public void testBackGesture_max_scrimAtMinScale() {
105         mNotificationPanelViewController.onBackProgressed(1.0f);
106         verify(mScrimController).applyBackScaling(
107                 NotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE);
108     }
109 
110     @Test
onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications()111     public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() {
112         mStatusBarStateController.setState(KEYGUARD);
113         ArgumentCaptor<OnHeightChangedListener> captor =
114                 ArgumentCaptor.forClass(OnHeightChangedListener.class);
115         verify(mNotificationStackScrollLayoutController)
116                 .setOnHeightChangedListener(captor.capture());
117         OnHeightChangedListener listener = captor.getValue();
118 
119         clearInvocations(mNotificationStackSizeCalculator);
120         listener.onHeightChanged(mock(ExpandableView.class), false);
121 
122         verify(mNotificationStackSizeCalculator)
123                 .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat());
124     }
125 
126     @Test
onNotificationHeightChangeWhileInShadeWillNotComputeMaxKeyguardNotifications()127     public void onNotificationHeightChangeWhileInShadeWillNotComputeMaxKeyguardNotifications() {
128         mStatusBarStateController.setState(SHADE);
129         ArgumentCaptor<OnHeightChangedListener> captor =
130                 ArgumentCaptor.forClass(OnHeightChangedListener.class);
131         verify(mNotificationStackScrollLayoutController)
132                 .setOnHeightChangedListener(captor.capture());
133         OnHeightChangedListener listener = captor.getValue();
134 
135         clearInvocations(mNotificationStackSizeCalculator);
136         listener.onHeightChanged(mock(ExpandableView.class), false);
137 
138         verify(mNotificationStackSizeCalculator, never())
139                 .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat());
140     }
141 
142     @Test
computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax()143     public void computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax() {
144         when(mAmbientState.getFractionToShade()).thenReturn(0.5f);
145         mNotificationPanelViewController.setMaxDisplayedNotifications(-1);
146 
147         // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value
148         assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications())
149                 .isEqualTo(-1);
150     }
151 
152     @Test
computeMaxKeyguardNotifications_noTransition_updatesMax()153     public void computeMaxKeyguardNotifications_noTransition_updatesMax() {
154         when(mAmbientState.getFractionToShade()).thenReturn(0f);
155         mNotificationPanelViewController.setMaxDisplayedNotifications(-1);
156 
157         // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value
158         assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications())
159                 .isNotEqualTo(-1);
160     }
161 
162     @Test
163     @Ignore("b/261472011 - Test appears inconsistent across environments")
getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable()164     public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
165         setBottomPadding(/* stackScrollLayoutBottom= */ 180,
166                 /* lockIconPadding= */ 20,
167                 /* indicationPadding= */ 0,
168                 /* ambientPadding= */ 0);
169 
170         assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications())
171                 .isEqualTo(80);
172     }
173 
174     @Test
175     @Ignore("b/261472011 - Test appears inconsistent across environments")
getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable()176     public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() {
177         setBottomPadding(/* stackScrollLayoutBottom= */ 180,
178                 /* lockIconPadding= */ 0,
179                 /* indicationPadding= */ 30,
180                 /* ambientPadding= */ 0);
181 
182         assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications())
183                 .isEqualTo(70);
184     }
185 
186     @Test
187     @Ignore("b/261472011 - Test appears inconsistent across environments")
getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable()188     public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() {
189         setBottomPadding(/* stackScrollLayoutBottom= */ 180,
190                 /* lockIconPadding= */ 0,
191                 /* indicationPadding= */ 0,
192                 /* ambientPadding= */ 40);
193 
194         assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications())
195                 .isEqualTo(60);
196     }
197 
198     @Test
getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight()199     public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() {
200         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
201                 /* lockIconPadding= */ 20,
202                 /* indicationPadding= */ 0,
203                 /* ambientPadding= */ 0);
204 
205         when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5);
206         assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
207                 .isEqualTo(5);
208     }
209 
210     @Test
getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero()211     public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() {
212         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
213                 /* lockIconPadding= */ 0,
214                 /* indicationPadding= */ 30,
215                 /* ambientPadding= */ 0);
216 
217         when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5);
218         assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
219                 .isEqualTo(0);
220     }
221 
222     @Test
getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero()223     public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() {
224         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
225                 /* lockIconPadding= */ 0,
226                 /* indicationPadding= */ 0,
227                 /* ambientPadding= */ 40);
228 
229         when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5);
230         assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
231                 .isEqualTo(0);
232     }
233 
234     @Test
getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight()235     public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() {
236         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
237                 /* lockIconPadding= */ 10,
238                 /* indicationPadding= */ 8,
239                 /* ambientPadding= */ 0);
240 
241         when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5);
242         assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
243                 .isEqualTo(2);
244     }
245 
246     @Test
testSetPanelScrimMinFractionWhenHeadsUpIsDragged()247     public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() {
248         mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
249                 mNotificationPanelViewController.getMaxPanelHeight() / 2);
250         verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
251     }
252 
253     @Test
testSetDozing_notifiesNsslAndStateController()254     public void testSetDozing_notifiesNsslAndStateController() {
255         mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */);
256         verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false));
257         assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f);
258     }
259 
260     @Test
testOnDozeAmountChanged_positionClockAndNotificationsUsesUdfpsLocation()261     public void testOnDozeAmountChanged_positionClockAndNotificationsUsesUdfpsLocation() {
262         // GIVEN UDFPS is enrolled and we're on the keyguard
263         final Point udfpsLocationCenter = new Point(0, 100);
264         final float udfpsRadius = 10f;
265         when(mUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
266         when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocationCenter);
267         when(mAuthController.getUdfpsRadius()).thenReturn(udfpsRadius);
268         mNotificationPanelViewController.getStatusBarStateListener().onStateChanged(KEYGUARD);
269 
270         // WHEN the doze amount changes
271         mNotificationPanelViewController.mClockPositionAlgorithm = mock(
272                 KeyguardClockPositionAlgorithm.class);
273         mNotificationPanelViewController.getStatusBarStateListener().onDozeAmountChanged(1f, 1f);
274 
275         // THEN the clock positions accounts for the UDFPS location & its worst case burn in
276         final float udfpsTop = udfpsLocationCenter.y - udfpsRadius - mMaxUdfpsBurnInOffsetY;
277         verify(mNotificationPanelViewController.mClockPositionAlgorithm).setup(
278                 anyInt(),
279                 anyFloat(),
280                 anyInt(),
281                 anyInt(),
282                 anyInt(),
283                 /* darkAmount */ eq(1f),
284                 anyFloat(),
285                 anyBoolean(),
286                 anyInt(),
287                 anyFloat(),
288                 anyInt(),
289                 anyBoolean(),
290                 /* udfpsTop */ eq(udfpsTop),
291                 anyFloat(),
292                 anyBoolean()
293         );
294     }
295 
296 
297     @Test
testSetExpandedHeight()298     public void testSetExpandedHeight() {
299         mNotificationPanelViewController.setExpandedHeight(200);
300         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
301     }
302 
303     @Test
testOnTouchEvent_expansionCanBeBlocked()304     public void testOnTouchEvent_expansionCanBeBlocked() {
305         onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
306         onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 200f, 0));
307         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
308 
309         mNotificationPanelViewController.blockExpansionForCurrentTouch();
310         onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 300f, 0));
311         // Expansion should not have changed because it was blocked
312         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
313     }
314 
315     @Test
test_pulsing_onTouchEvent_noTracking()316     public void test_pulsing_onTouchEvent_noTracking() {
317         // GIVEN device is pulsing
318         mNotificationPanelViewController.setPulsing(true);
319 
320         // WHEN touch DOWN & MOVE events received
321         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
322                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
323                 0 /* metaState */));
324         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
325                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
326                 0 /* metaState */));
327 
328         // THEN touch is NOT tracked (since the device is pulsing)
329         assertThat(mNotificationPanelViewController.isTracking()).isFalse();
330     }
331 
332     @Test
test_onTouchEvent_startTracking()333     public void test_onTouchEvent_startTracking() {
334         // GIVEN device is NOT pulsing
335         mNotificationPanelViewController.setPulsing(false);
336 
337         // WHEN touch DOWN & MOVE events received
338         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
339                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
340                 0 /* metaState */));
341         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
342                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
343                 0 /* metaState */));
344 
345         // THEN touch is tracked
346         assertThat(mNotificationPanelViewController.isTracking()).isTrue();
347     }
348 
349     @Test
testOnTouchEvent_expansionResumesAfterBriefTouch()350     public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
351         mFalsingManager.setIsClassifierEnabled(true);
352         mFalsingManager.setIsFalseTouch(false);
353         // Start shade collapse with swipe up
354         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
355                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
356                 0 /* metaState */));
357         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
358                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
359                 0 /* metaState */));
360         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
361                 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
362                 0 /* metaState */));
363 
364         assertThat(mNotificationPanelViewController.isClosing()).isTrue();
365         assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
366 
367         // simulate touch that does not exceed touch slop
368         onTouchEvent(MotionEvent.obtain(2L /* downTime */,
369                 2L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 300f /* y */,
370                 0 /* metaState */));
371 
372         mNotificationPanelViewController.setTouchSlopExceeded(false);
373 
374         onTouchEvent(MotionEvent.obtain(2L /* downTime */,
375                 2L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
376                 0 /* metaState */));
377 
378         // fling should still be called after a touch that does not exceed touch slop
379         assertThat(mNotificationPanelViewController.isClosing()).isTrue();
380         assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
381     }
382 
383     @Test
testA11y_initializeNode()384     public void testA11y_initializeNode() {
385         AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
386         mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
387 
388         List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
389         assertThat(actionList).containsAtLeastElementsIn(
390                 new AccessibilityNodeInfo.AccessibilityAction[] {
391                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD,
392                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP}
393         );
394     }
395 
396     @Test
testA11y_scrollForward()397     public void testA11y_scrollForward() {
398         mAccessibilityDelegate.performAccessibilityAction(
399                 mView,
400                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
401                 null);
402 
403         verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
404     }
405 
406     @Test
testA11y_scrollUp()407     public void testA11y_scrollUp() {
408         mAccessibilityDelegate.performAccessibilityAction(
409                 mView,
410                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
411                 null);
412 
413         verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
414     }
415 
416     @Test
testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications()417     public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() {
418         mStatusBarStateController.setState(KEYGUARD);
419         enableSplitShade(/* enabled= */ true);
420 
421         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
422         mNotificationPanelViewController.updateResources();
423         assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
424                 .isEqualTo(R.id.qs_edge_guideline);
425 
426         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
427         mNotificationPanelViewController.updateResources();
428         assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
429                 .isEqualTo(ConstraintSet.PARENT_ID);
430     }
431 
432     @Test
keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered()433     public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() {
434         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
435         mStatusBarStateController.setState(KEYGUARD);
436         enableSplitShade(/* enabled= */ true);
437 
438         setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ true);
439 
440         assertKeyguardStatusViewCentered();
441     }
442 
443     @Test
keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered()444     public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() {
445         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
446         mStatusBarStateController.setState(KEYGUARD);
447         enableSplitShade(/* enabled= */ true);
448 
449         setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ false);
450 
451         assertKeyguardStatusViewNotCentered();
452     }
453 
454     @Test
keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered()455     public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() {
456         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
457         mStatusBarStateController.setState(KEYGUARD);
458         enableSplitShade(/* enabled= */ true);
459 
460         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
461 
462         assertKeyguardStatusViewNotCentered();
463     }
464 
465     @Test
keyguardStatusView_splitShade_pulsing_isNotCentered()466     public void keyguardStatusView_splitShade_pulsing_isNotCentered() {
467         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
468         when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(true);
469         mStatusBarStateController.setState(KEYGUARD);
470         enableSplitShade(/* enabled= */ true);
471 
472         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
473 
474         assertKeyguardStatusViewNotCentered();
475     }
476 
477     @Test
keyguardStatusView_splitShade_notPulsing_isNotCentered()478     public void keyguardStatusView_splitShade_notPulsing_isNotCentered() {
479         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
480         when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
481         mStatusBarStateController.setState(KEYGUARD);
482         enableSplitShade(/* enabled= */ true);
483 
484         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
485 
486         assertKeyguardStatusViewNotCentered();
487     }
488 
489     @Test
keyguardStatusView_singleShade_isCentered()490     public void keyguardStatusView_singleShade_isCentered() {
491         enableSplitShade(/* enabled= */ false);
492         // The conditions below would make the clock NOT be centered on split shade.
493         // On single shade it should always be centered though.
494         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
495         when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
496         mStatusBarStateController.setState(KEYGUARD);
497         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
498 
499         assertKeyguardStatusViewCentered();
500     }
501 
502     @Test
keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot()503     public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() {
504         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
505         mStatusBarStateController.setState(KEYGUARD);
506         enableSplitShade(/* enabled= */ true);
507 
508         mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
509         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
510         assertKeyguardStatusViewCentered();
511 
512         mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
513         assertKeyguardStatusViewNotCentered();
514     }
515 
516     @Test
keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController()517     public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() {
518         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
519         mStatusBarStateController.setState(KEYGUARD);
520         enableSplitShade(/* enabled= */ true);
521 
522         mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
523 
524         verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true);
525     }
526 
527     @Test
keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs()528     public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
529         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
530         mStatusBarStateController.setState(KEYGUARD);
531         enableSplitShade(/* enabled= */ true);
532 
533         mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
534         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
535         assertKeyguardStatusViewCentered();
536 
537         mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
538         assertKeyguardStatusViewCentered();
539     }
540 
541     @Test
onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL()542     public void onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL() {
543         ArgumentCaptor<View.OnLayoutChangeListener> captor =
544                 ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
545         verify(mKeyguardStatusView).addOnLayoutChangeListener(captor.capture());
546         View.OnLayoutChangeListener listener = captor.getValue();
547 
548         clearInvocations(mNotificationStackScrollLayoutController);
549 
550         when(mKeyguardStatusView.getHeight()).thenReturn(0);
551         listener.onLayoutChange(mKeyguardStatusView, /* left= */ 0, /* top= */ 0, /* right= */
552                 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
553                 0, /* oldBottom = */ 200);
554 
555         verify(mNotificationStackScrollLayoutController).animateNextTopPaddingChange();
556     }
557 
558     @Test
testCanCollapsePanelOnTouch_trueForKeyGuard()559     public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
560         mStatusBarStateController.setState(KEYGUARD);
561 
562         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
563     }
564 
565     @Test
testCanCollapsePanelOnTouch_trueWhenScrolledToBottom()566     public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() {
567         mStatusBarStateController.setState(SHADE);
568         when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true);
569 
570         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
571     }
572 
573     @Test
testCanCollapsePanelOnTouch_trueWhenInSettings()574     public void testCanCollapsePanelOnTouch_trueWhenInSettings() {
575         mStatusBarStateController.setState(SHADE);
576         when(mQsController.getExpanded()).thenReturn(true);
577 
578         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
579     }
580 
581     @Test
testCanCollapsePanelOnTouch_falseInDualPaneShade()582     public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
583         mStatusBarStateController.setState(SHADE);
584         enableSplitShade(/* enabled= */ true);
585         when(mQsController.getExpanded()).thenReturn(true);
586 
587         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse();
588     }
589 
590     @Test
testSwipeWhileLocked_notifiesKeyguardState()591     public void testSwipeWhileLocked_notifiesKeyguardState() {
592         mStatusBarStateController.setState(KEYGUARD);
593 
594         // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that
595         // the fling occurred and did not dismiss the keyguard.
596         mNotificationPanelViewController.flingToHeight(
597                 0f, true /* expand */, 1000f, 1f, false);
598         verify(mKeyguardStateController).notifyPanelFlingStart(false /* dismissKeyguard */);
599 
600         // Fling un-expanded, which is a keyguard exit fling when we're in KEYGUARD state.
601         mNotificationPanelViewController.flingToHeight(
602                 0f, false /* expand */, 1000f, 1f, false);
603         verify(mKeyguardStateController).notifyPanelFlingStart(true /* dismissKeyguard */);
604     }
605 
606     @Test
testCancelSwipeWhileLocked_notifiesKeyguardState()607     public void testCancelSwipeWhileLocked_notifiesKeyguardState() {
608         mStatusBarStateController.setState(KEYGUARD);
609 
610         // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that
611         // the fling occurred and did not dismiss the keyguard.
612         mNotificationPanelViewController.flingToHeight(
613                 0f, true /* expand */, 1000f, 1f, false);
614         mNotificationPanelViewController.cancelHeightAnimator();
615         verify(mKeyguardStateController).notifyPanelFlingEnd();
616     }
617 
618     @Test
testSwipe_exactlyToTarget_notifiesNssl()619     public void testSwipe_exactlyToTarget_notifiesNssl() {
620         // No over-expansion
621         mNotificationPanelViewController.setOverExpansion(0f);
622         // Fling to a target that is equal to the current position (i.e. a no-op fling).
623         mNotificationPanelViewController.flingToHeight(
624                 0f,
625                 true,
626                 mNotificationPanelViewController.getExpandedHeight(),
627                 1f,
628                 false);
629         // Verify that the NSSL is notified that the panel is *not* flinging.
630         verify(mNotificationStackScrollLayoutController).setPanelFlinging(false);
631     }
632 
633     @Test
testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked()634     public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() {
635         mStatusBarStateController.setState(KEYGUARD);
636         when(mQsController.getExpanded()).thenReturn(true);
637 
638         enableSplitShade(true);
639 
640         assertThat(mStatusBarStateController.getState()).isEqualTo(SHADE_LOCKED);
641     }
642 
643     @Test
testUnlockedSplitShadeTransitioningToKeyguard_closesQS()644     public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
645         enableSplitShade(true);
646         mStatusBarStateController.setState(SHADE);
647         mStatusBarStateController.setState(KEYGUARD);
648 
649         verify(mQsController).closeQs();
650     }
651 
652     @Test
testLockedSplitShadeTransitioningToKeyguard_closesQS()653     public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
654         enableSplitShade(true);
655         mStatusBarStateController.setState(SHADE_LOCKED);
656         mStatusBarStateController.setState(KEYGUARD);
657 
658         verify(mQsController).closeQs();
659     }
660 
661     @Test
testSwitchesToCorrectClockInSinglePaneShade()662     public void testSwitchesToCorrectClockInSinglePaneShade() {
663         mStatusBarStateController.setState(KEYGUARD);
664 
665         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
666         triggerPositionClockAndNotifications();
667         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
668 
669         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
670         triggerPositionClockAndNotifications();
671         verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
672     }
673 
674     @Test
testSwitchesToCorrectClockInSplitShade()675     public void testSwitchesToCorrectClockInSplitShade() {
676         mStatusBarStateController.setState(KEYGUARD);
677         enableSplitShade(/* enabled= */ true);
678         clearInvocations(mKeyguardStatusViewController);
679 
680         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
681         triggerPositionClockAndNotifications();
682         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
683 
684         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
685         triggerPositionClockAndNotifications();
686         verify(mKeyguardStatusViewController, times(2))
687                 .displayClock(LARGE, /* animate */ true);
688         verify(mKeyguardStatusViewController, never())
689                 .displayClock(SMALL, /* animate */ true);
690     }
691 
692     @Test
testHasNotifications_switchesToLargeClockWhenEnteringSplitShade()693     public void testHasNotifications_switchesToLargeClockWhenEnteringSplitShade() {
694         mStatusBarStateController.setState(KEYGUARD);
695         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
696 
697         enableSplitShade(/* enabled= */ true);
698 
699         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
700     }
701 
702     @Test
testNoNotifications_switchesToLargeClockWhenEnteringSplitShade()703     public void testNoNotifications_switchesToLargeClockWhenEnteringSplitShade() {
704         mStatusBarStateController.setState(KEYGUARD);
705         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
706 
707         enableSplitShade(/* enabled= */ true);
708 
709         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
710     }
711 
712     @Test
testHasNotifications_switchesToSmallClockWhenExitingSplitShade()713     public void testHasNotifications_switchesToSmallClockWhenExitingSplitShade() {
714         mStatusBarStateController.setState(KEYGUARD);
715         enableSplitShade(/* enabled= */ true);
716         clearInvocations(mKeyguardStatusViewController);
717         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
718 
719         enableSplitShade(/* enabled= */ false);
720 
721         verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
722     }
723 
724     @Test
testNoNotifications_switchesToLargeClockWhenExitingSplitShade()725     public void testNoNotifications_switchesToLargeClockWhenExitingSplitShade() {
726         mStatusBarStateController.setState(KEYGUARD);
727         enableSplitShade(/* enabled= */ true);
728         clearInvocations(mKeyguardStatusViewController);
729         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
730 
731         enableSplitShade(/* enabled= */ false);
732 
733         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
734     }
735 
736     @Test
clockSize_mediaShowing_inSplitShade_onAod_isLarge()737     public void clockSize_mediaShowing_inSplitShade_onAod_isLarge() {
738         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
739         mStatusBarStateController.setState(KEYGUARD);
740         enableSplitShade(/* enabled= */ true);
741         when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
742         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
743         clearInvocations(mKeyguardStatusViewController);
744 
745         mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
746 
747         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate= */ true);
748     }
749 
750     @Test
clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall()751     public void clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall() {
752         when(mDozeParameters.getAlwaysOn()).thenReturn(false);
753         mStatusBarStateController.setState(KEYGUARD);
754         enableSplitShade(/* enabled= */ true);
755         when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
756         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
757         clearInvocations(mKeyguardStatusViewController);
758 
759         mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
760 
761         verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate= */ true);
762     }
763 
764     @Test
onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL()765     public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL() {
766         // GIVEN
767         mStatusBarStateController.setState(KEYGUARD);
768         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
769         when(mQsController.getFullyExpanded()).thenReturn(true);
770         when(mQsController.getExpanded()).thenReturn(true);
771 
772         // WHEN
773         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
774         mNotificationPanelViewController.setExpandedHeight(transitionDistance);
775 
776         // THEN
777         // We are interested in the last value of the stack alpha.
778         ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
779         verify(mNotificationStackScrollLayoutController, atLeastOnce())
780                 .setAlpha(alphaCaptor.capture());
781         assertThat(alphaCaptor.getValue()).isEqualTo(1.0f);
782     }
783 
784     @Test
onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL()785     public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL() {
786         // GIVEN
787         mStatusBarStateController.setState(KEYGUARD);
788         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
789         when(mQsController.getFullyExpanded()).thenReturn(false);
790         when(mQsController.getExpanded()).thenReturn(true);
791 
792         // WHEN
793         int transitionDistance = mNotificationPanelViewController
794                 .getMaxPanelTransitionDistance() / 2;
795         mNotificationPanelViewController.setExpandedHeight(transitionDistance);
796 
797         // THEN
798         // We are interested in the last value of the stack alpha.
799         ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
800         verify(mNotificationStackScrollLayoutController, atLeastOnce())
801                 .setAlpha(alphaCaptor.capture());
802         assertThat(alphaCaptor.getValue()).isEqualTo(0.0f);
803     }
804 
805     @Test
testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled()806     public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
807         when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
808         mStatusBarStateController.setState(KEYGUARD);
809         enableSplitShade(/* enabled= */ true);
810         clearInvocations(mKeyguardStatusViewController);
811         when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
812         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
813 
814         mNotificationPanelViewController.setDozing(true, false);
815 
816         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
817     }
818 
819     @Test
testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying()820     public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() {
821         mStatusBarStateController.setState(KEYGUARD);
822         enableSplitShade(/* enabled= */ true);
823         clearInvocations(mKeyguardStatusViewController);
824         when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
825 
826         // one notification + media player visible
827         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
828         triggerPositionClockAndNotifications();
829         verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
830 
831         // only media player visible
832         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
833         triggerPositionClockAndNotifications();
834         verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true);
835         verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true);
836     }
837 
838     @Test
testFoldToAodAnimationCleansupInAnimationEnd()839     public void testFoldToAodAnimationCleansupInAnimationEnd() {
840         ArgumentCaptor<Animator.AnimatorListener> animCaptor =
841                 ArgumentCaptor.forClass(Animator.AnimatorListener.class);
842         ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> updateCaptor =
843                 ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
844 
845         // Start fold animation & Capture Listeners
846         mNotificationPanelViewController.getShadeFoldAnimator()
847                 .startFoldToAodAnimation(() -> {}, () -> {}, () -> {});
848         verify(mViewPropertyAnimator).setListener(animCaptor.capture());
849         verify(mViewPropertyAnimator).setUpdateListener(updateCaptor.capture());
850 
851         // End animation and validate listeners were unset
852         animCaptor.getValue().onAnimationEnd(null);
853         verify(mViewPropertyAnimator).setListener(null);
854         verify(mViewPropertyAnimator).setUpdateListener(null);
855     }
856 
857     @Test
testExpandWithQsMethodIsUsingLockscreenTransitionController()858     public void testExpandWithQsMethodIsUsingLockscreenTransitionController() {
859         enableSplitShade(/* enabled= */ true);
860         mStatusBarStateController.setState(KEYGUARD);
861 
862         mNotificationPanelViewController.expandToQs();
863 
864         verify(mLockscreenShadeTransitionController).goToLockedShade(
865                 /* expandedView= */null, /* needsQSAnimation= */true);
866     }
867 
868     @Test
testUnlockAnimationDoesNotAffectScrim()869     public void testUnlockAnimationDoesNotAffectScrim() {
870         mNotificationPanelViewController.onUnlockHintStarted();
871         verify(mScrimController).setExpansionAffectsAlpha(false);
872         mNotificationPanelViewController.onUnlockHintFinished();
873         verify(mScrimController).setExpansionAffectsAlpha(true);
874     }
875 
876     @Test
testUnlockHintAnimation_runs_whenNotInPowerSaveMode_andDozeAmountIsZero()877     public void testUnlockHintAnimation_runs_whenNotInPowerSaveMode_andDozeAmountIsZero() {
878         when(mPowerManager.isPowerSaveMode()).thenReturn(false);
879         when(mAmbientState.getDozeAmount()).thenReturn(0f);
880         mNotificationPanelViewController.startUnlockHintAnimation();
881         assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isTrue();
882     }
883 
884     @Test
testUnlockHintAnimation_doesNotRun_inPowerSaveMode()885     public void testUnlockHintAnimation_doesNotRun_inPowerSaveMode() {
886         when(mPowerManager.isPowerSaveMode()).thenReturn(true);
887         mNotificationPanelViewController.startUnlockHintAnimation();
888         assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse();
889     }
890 
891     @Test
testUnlockHintAnimation_doesNotRun_whenDozeAmountNotZero()892     public void testUnlockHintAnimation_doesNotRun_whenDozeAmountNotZero() {
893         when(mPowerManager.isPowerSaveMode()).thenReturn(false);
894         when(mAmbientState.getDozeAmount()).thenReturn(0.5f);
895         mNotificationPanelViewController.startUnlockHintAnimation();
896         assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse();
897     }
898 
899     @Test
setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController()900     public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() {
901         float statusBarAlpha = 0.5f;
902 
903         mNotificationPanelViewController.setKeyguardStatusBarAlpha(statusBarAlpha);
904 
905         verify(mKeyguardStatusBarViewController).setAlpha(statusBarAlpha);
906     }
907 
908     @Test
testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade()909     public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
910         enableSplitShade(/* enabled= */ true);
911         mShadeExpansionStateManager.updateState(STATE_OPEN);
912         verify(mQsController).setExpandImmediate(false);
913 
914         mShadeExpansionStateManager.updateState(STATE_CLOSED);
915         verify(mQsController, times(2)).setExpandImmediate(false);
916 
917         mShadeExpansionStateManager.updateState(STATE_OPENING);
918         verify(mQsController).setExpandImmediate(true);
919     }
920 
921     @Test
testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked()922     public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() {
923         enableSplitShade(/* enabled= */ true);
924         mShadeExpansionStateManager.updateState(STATE_CLOSED);
925 
926         mStatusBarStateController.setState(KEYGUARD);
927         // going to lockscreen would trigger STATE_OPENING
928         mShadeExpansionStateManager.updateState(STATE_OPENING);
929 
930         verify(mQsController, never()).setExpandImmediate(true);
931     }
932 
933     @Test
testQsImmediateResetsWhenPanelOpensOrCloses()934     public void testQsImmediateResetsWhenPanelOpensOrCloses() {
935         mShadeExpansionStateManager.updateState(STATE_OPEN);
936         mShadeExpansionStateManager.updateState(STATE_CLOSED);
937         verify(mQsController, times(2)).setExpandImmediate(false);
938     }
939 
940     @Test
testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade()941     public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
942         // to make sure shade is in expanded state
943         mNotificationPanelViewController.startWaitingForExpandGesture();
944 
945         // switch to split shade from portrait (default state)
946         enableSplitShade(/* enabled= */ true);
947         verify(mQsController).setExpanded(true);
948 
949         // switch to portrait from split shade
950         enableSplitShade(/* enabled= */ false);
951         verify(mQsController).setExpanded(false);
952     }
953 
954     @Test
testPanelClosedWhenClosingQsInSplitShade()955     public void testPanelClosedWhenClosingQsInSplitShade() {
956         mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
957                 /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
958         enableSplitShade(/* enabled= */ true);
959         mNotificationPanelViewController.setExpandedFraction(1f);
960 
961         assertThat(mNotificationPanelViewController.isClosing()).isFalse();
962         mNotificationPanelViewController.animateCollapseQs(false);
963 
964         assertThat(mNotificationPanelViewController.isClosing()).isTrue();
965     }
966 
967     @Test
getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance()968     public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
969         enableSplitShade(true);
970         mNotificationPanelViewController.expandToQs();
971 
972         int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
973 
974         assertThat(maxDistance).isEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
975     }
976 
977     @Test
isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress()978     public void isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress() {
979         when(mQsController.getLockscreenShadeDragProgress()).thenReturn(0.5f);
980         assertThat(mNotificationPanelViewController.isExpandingOrCollapsing()).isTrue();
981     }
982 
983 
984     @Test
getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue()985     public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
986         enableSplitShade(true);
987         mNotificationPanelViewController.expandToQs();
988         when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true);
989         when(mQsController.calculatePanelHeightExpanded(anyInt())).thenReturn(10000);
990         mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
991                 SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
992 
993         int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
994 
995         // make sure we're ignoring the placeholder value for Qs max height
996         assertThat(maxDistance).isLessThan(10000);
997         assertThat(maxDistance).isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
998     }
999 
1000     @Test
getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue()1001     public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() {
1002         mStatusBarStateController.setState(KEYGUARD);
1003         enableSplitShade(true);
1004         mNotificationPanelViewController.expandToQs();
1005 
1006         int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
1007 
1008         assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
1009     }
1010 
1011     @Test
getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue()1012     public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() {
1013         enableSplitShade(false);
1014         mNotificationPanelViewController.expandToQs();
1015 
1016         int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
1017 
1018         assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
1019     }
1020 
1021     @Test
onLayoutChange_fullWidth_updatesQSWithFullWithTrue()1022     public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
1023         setIsFullWidth(true);
1024 
1025         verify(mQsController).setNotificationPanelFullWidth(true);
1026     }
1027 
1028     @Test
onLayoutChange_notFullWidth_updatesQSWithFullWithFalse()1029     public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
1030         setIsFullWidth(false);
1031 
1032         verify(mQsController).setNotificationPanelFullWidth(false);
1033     }
1034 
1035     @Test
onLayoutChange_qsNotSet_doesNotCrash()1036     public void onLayoutChange_qsNotSet_doesNotCrash() {
1037         mQuickSettingsController.setQs(null);
1038 
1039         triggerLayoutChange();
1040     }
1041 
1042     @Test
onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth()1043     public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
1044         StatusBarStateController.StateListener statusBarStateListener =
1045                 mNotificationPanelViewController.getStatusBarStateListener();
1046         statusBarStateListener.onStateChanged(KEYGUARD);
1047         mNotificationPanelViewController.setDozing(false, false);
1048 
1049         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
1050         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
1051         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
1052 
1053         verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked();
1054         verify(mUpdateMonitor).requestFaceAuth(
1055                 FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
1056     }
1057 
1058     @Test
onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation()1059     public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
1060         StatusBarStateController.StateListener statusBarStateListener =
1061                 mNotificationPanelViewController.getStatusBarStateListener();
1062         statusBarStateListener.onStateChanged(KEYGUARD);
1063         mNotificationPanelViewController.setDozing(false, false);
1064         when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
1065 
1066         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
1067         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
1068         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
1069 
1070         verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true);
1071     }
1072 
1073     @Test
onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint()1074     public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
1075         StatusBarStateController.StateListener statusBarStateListener =
1076                 mNotificationPanelViewController.getStatusBarStateListener();
1077         statusBarStateListener.onStateChanged(KEYGUARD);
1078         mNotificationPanelViewController.setDozing(false, false);
1079         when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
1080 
1081         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
1082         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
1083         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
1084 
1085         verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true);
1086     }
1087 
1088     @Test
onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth()1089     public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
1090         StatusBarStateController.StateListener statusBarStateListener =
1091                 mNotificationPanelViewController.getStatusBarStateListener();
1092         statusBarStateListener.onStateChanged(KEYGUARD);
1093         mNotificationPanelViewController.setDozing(true, false);
1094 
1095         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
1096         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
1097         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
1098 
1099         verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
1100     }
1101 
1102     @Test
onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth()1103     public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
1104         StatusBarStateController.StateListener statusBarStateListener =
1105                 mNotificationPanelViewController.getStatusBarStateListener();
1106         statusBarStateListener.onStateChanged(SHADE_LOCKED);
1107 
1108         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
1109 
1110         verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
1111 
1112     }
1113 
1114     @Test
onSplitShadeChanged_duringShadeExpansion_resetsOverScrollState()1115     public void onSplitShadeChanged_duringShadeExpansion_resetsOverScrollState() {
1116         // There was a bug where there was left-over overscroll state after going from split shade
1117         // to single shade.
1118         // Since on single shade we don't set overscroll values on QS nor Scrim, those values that
1119         // were there from split shade were never reset.
1120         // To prevent this, we will reset all overscroll state.
1121         enableSplitShade(true);
1122         reset(mQsController, mScrimController, mNotificationStackScrollLayoutController);
1123 
1124         mNotificationPanelViewController.setOverExpansion(123);
1125         verify(mQsController).setOverScrollAmount(123);
1126         verify(mScrimController).setNotificationsOverScrollAmount(123);
1127         verify(mNotificationStackScrollLayoutController).setOverExpansion(123);
1128 
1129         enableSplitShade(false);
1130         verify(mQsController).setOverScrollAmount(0);
1131         verify(mScrimController).setNotificationsOverScrollAmount(0);
1132         verify(mNotificationStackScrollLayoutController).setOverExpansion(0);
1133     }
1134 
1135     @Test
onSplitShadeChanged_alwaysResetsOverScrollState()1136     public void onSplitShadeChanged_alwaysResetsOverScrollState() {
1137         enableSplitShade(true);
1138         enableSplitShade(false);
1139 
1140         verify(mQsController, times(2)).setOverScrollAmount(0);
1141         verify(mScrimController, times(2)).setNotificationsOverScrollAmount(0);
1142         verify(mNotificationStackScrollLayoutController, times(2)).setOverExpansion(0);
1143         verify(mNotificationStackScrollLayoutController, times(2)).setOverScrollAmount(0);
1144     }
1145 
1146     /**
1147      * When shade is flinging to close and this fling is not intercepted,
1148      * {@link AmbientState#setIsClosing(boolean)} should be called before
1149      * {@link NotificationStackScrollLayoutController#onExpansionStopped()}
1150      * to ensure scrollY can be correctly set to be 0
1151      */
1152     @Test
onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped()1153     public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
1154         // Given: Shade is expanded
1155         mNotificationPanelViewController.notifyExpandingFinished();
1156         mNotificationPanelViewController.setClosing(false);
1157 
1158         // When: Shade flings to close not canceled
1159         mNotificationPanelViewController.notifyExpandingStarted();
1160         mNotificationPanelViewController.setClosing(true);
1161         mNotificationPanelViewController.onFlingEnd(false);
1162 
1163         // Then: AmbientState's mIsClosing should be set to false
1164         // before mNotificationStackScrollLayoutController.onExpansionStopped() is called
1165         // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition
1166         // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed
1167         InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController);
1168         inOrder.verify(mAmbientState).setIsClosing(false);
1169         inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped();
1170     }
1171 
1172     @Test
onShadeFlingEnd_mExpandImmediateShouldBeReset()1173     public void onShadeFlingEnd_mExpandImmediateShouldBeReset() {
1174         mNotificationPanelViewController.onFlingEnd(false);
1175 
1176         verify(mQsController).setExpandImmediate(false);
1177     }
1178 
1179     @Test
inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded()1180     public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() {
1181         mStatusBarStateController.setState(SHADE);
1182         enableSplitShade(true);
1183         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
1184         mNotificationPanelViewController.setExpandedHeight(transitionDistance);
1185         assertThat(mNotificationPanelViewController.isFullyExpanded()).isTrue();
1186     }
1187 
1188     @Test
shadeFullyExpanded_inShadeState()1189     public void shadeFullyExpanded_inShadeState() {
1190         mStatusBarStateController.setState(SHADE);
1191 
1192         mNotificationPanelViewController.setExpandedHeight(0);
1193         assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isFalse();
1194 
1195         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
1196         mNotificationPanelViewController.setExpandedHeight(transitionDistance);
1197         assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
1198     }
1199 
1200     @Test
shadeFullyExpanded_onKeyguard()1201     public void shadeFullyExpanded_onKeyguard() {
1202         mStatusBarStateController.setState(KEYGUARD);
1203 
1204         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
1205         mNotificationPanelViewController.setExpandedHeight(transitionDistance);
1206         assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isFalse();
1207     }
1208 
1209     @Test
shadeFullyExpanded_onShadeLocked()1210     public void shadeFullyExpanded_onShadeLocked() {
1211         mStatusBarStateController.setState(SHADE_LOCKED);
1212         assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
1213     }
1214 
1215     @Test
shadeExpanded_whenHasHeight()1216     public void shadeExpanded_whenHasHeight() {
1217         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
1218         mNotificationPanelViewController.setExpandedHeight(transitionDistance);
1219         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
1220     }
1221 
1222     @Test
shadeExpanded_whenInstantExpanding()1223     public void shadeExpanded_whenInstantExpanding() {
1224         mNotificationPanelViewController.expand(true);
1225         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
1226     }
1227 
1228     @Test
shadeExpanded_whenHunIsPresent()1229     public void shadeExpanded_whenHunIsPresent() {
1230         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
1231         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
1232     }
1233 
1234     @Test
shadeExpanded_whenWaitingForExpandGesture()1235     public void shadeExpanded_whenWaitingForExpandGesture() {
1236         mNotificationPanelViewController.startWaitingForExpandGesture();
1237         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
1238     }
1239 
1240     @Test
shadeExpanded_whenUnlockedOffscreenAnimationRunning()1241     public void shadeExpanded_whenUnlockedOffscreenAnimationRunning() {
1242         when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
1243         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
1244     }
1245 
1246     @Test
getFalsingThreshold_deviceNotInteractive_isQsThreshold()1247     public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() {
1248         mFakeKeyguardRepository.setWakefulnessModel(
1249                 new WakefulnessModel(
1250                         WakefulnessState.ASLEEP,
1251                         /* lastWakeReason= */ WakeSleepReason.TAP,
1252                         /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
1253         );
1254         when(mQsController.getFalsingThreshold()).thenReturn(14);
1255 
1256         assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
1257     }
1258 
1259     @Test
getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold()1260     public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() {
1261         mFakeKeyguardRepository.setWakefulnessModel(
1262                 new WakefulnessModel(
1263                         WakefulnessState.AWAKE,
1264                         /* lastWakeReason= */ WakeSleepReason.POWER_BUTTON,
1265                         /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
1266         );
1267         when(mQsController.getFalsingThreshold()).thenReturn(14);
1268 
1269         assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
1270     }
1271 
1272     @Test
getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold()1273     public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() {
1274         mFakeKeyguardRepository.setWakefulnessModel(
1275                 new WakefulnessModel(
1276                         WakefulnessState.AWAKE,
1277                         /* lastWakeReason= */ WakeSleepReason.TAP,
1278                         /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
1279         );
1280         when(mQsController.getFalsingThreshold()).thenReturn(14);
1281 
1282         assertThat(mNotificationPanelViewController.getFalsingThreshold()).isGreaterThan(14);
1283     }
1284 }
1285