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.statusbar.phone;
18 
19 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
20 
21 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
22 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
23 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
24 import static com.android.systemui.statusbar.StatusBarState.SHADE;
25 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
26 import static com.android.systemui.statusbar.notification.ViewGroupFadeHelper.reset;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyBoolean;
32 import static org.mockito.ArgumentMatchers.anyInt;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.Mockito.atLeast;
35 import static org.mockito.Mockito.atLeastOnce;
36 import static org.mockito.Mockito.clearInvocations;
37 import static org.mockito.Mockito.doAnswer;
38 import static org.mockito.Mockito.mock;
39 import static org.mockito.Mockito.never;
40 import static org.mockito.Mockito.times;
41 import static org.mockito.Mockito.verify;
42 import static org.mockito.Mockito.when;
43 
44 import android.annotation.IdRes;
45 import android.app.ActivityManager;
46 import android.content.ContentResolver;
47 import android.content.res.Configuration;
48 import android.content.res.Resources;
49 import android.database.ContentObserver;
50 import android.os.Handler;
51 import android.os.Looper;
52 import android.os.PowerManager;
53 import android.os.UserManager;
54 import android.testing.AndroidTestingRunner;
55 import android.testing.TestableLooper;
56 import android.util.DisplayMetrics;
57 import android.view.LayoutInflater;
58 import android.view.MotionEvent;
59 import android.view.View;
60 import android.view.ViewGroup;
61 import android.view.ViewPropertyAnimator;
62 import android.view.ViewStub;
63 import android.view.accessibility.AccessibilityManager;
64 import android.view.accessibility.AccessibilityNodeInfo;
65 
66 import androidx.constraintlayout.widget.ConstraintLayout;
67 import androidx.constraintlayout.widget.ConstraintSet;
68 import androidx.test.filters.SmallTest;
69 
70 import com.android.internal.logging.MetricsLogger;
71 import com.android.internal.logging.UiEventLogger;
72 import com.android.internal.logging.testing.UiEventLoggerFake;
73 import com.android.internal.util.CollectionUtils;
74 import com.android.internal.util.LatencyTracker;
75 import com.android.keyguard.KeyguardClockSwitch;
76 import com.android.keyguard.KeyguardClockSwitchController;
77 import com.android.keyguard.KeyguardStatusView;
78 import com.android.keyguard.KeyguardStatusViewController;
79 import com.android.keyguard.KeyguardUpdateMonitor;
80 import com.android.keyguard.LockIconViewController;
81 import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
82 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
83 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
84 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
85 import com.android.systemui.R;
86 import com.android.systemui.SysuiTestCase;
87 import com.android.systemui.biometrics.AuthController;
88 import com.android.systemui.classifier.FalsingCollectorFake;
89 import com.android.systemui.classifier.FalsingManagerFake;
90 import com.android.systemui.controls.dagger.ControlsComponent;
91 import com.android.systemui.doze.DozeLog;
92 import com.android.systemui.dump.DumpManager;
93 import com.android.systemui.flags.FeatureFlags;
94 import com.android.systemui.fragments.FragmentHostManager;
95 import com.android.systemui.fragments.FragmentService;
96 import com.android.systemui.media.KeyguardMediaController;
97 import com.android.systemui.media.MediaDataManager;
98 import com.android.systemui.media.MediaHierarchyManager;
99 import com.android.systemui.navigationbar.NavigationModeController;
100 import com.android.systemui.plugins.FalsingManager;
101 import com.android.systemui.qs.QSDetailDisplayer;
102 import com.android.systemui.screenrecord.RecordingController;
103 import com.android.systemui.statusbar.CommandQueue;
104 import com.android.systemui.statusbar.KeyguardAffordanceView;
105 import com.android.systemui.statusbar.KeyguardIndicationController;
106 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
107 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
108 import com.android.systemui.statusbar.NotificationRemoteInputManager;
109 import com.android.systemui.statusbar.NotificationShadeDepthController;
110 import com.android.systemui.statusbar.NotificationShelfController;
111 import com.android.systemui.statusbar.PulseExpansionHandler;
112 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
113 import com.android.systemui.statusbar.SysuiStatusBarStateController;
114 import com.android.systemui.statusbar.VibratorHelper;
115 import com.android.systemui.statusbar.events.PrivacyDotViewController;
116 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
117 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
118 import com.android.systemui.statusbar.notification.NotificationEntryManager;
119 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
120 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
121 import com.android.systemui.statusbar.notification.stack.AmbientState;
122 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
123 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
124 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
125 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
126 import com.android.systemui.statusbar.policy.ConfigurationController;
127 import com.android.systemui.statusbar.policy.KeyguardStateController;
128 import com.android.systemui.unfold.SysUIUnfoldComponent;
129 import com.android.systemui.util.concurrency.FakeExecutor;
130 import com.android.systemui.util.settings.SecureSettings;
131 import com.android.systemui.util.time.FakeSystemClock;
132 import com.android.systemui.wallet.controller.QuickAccessWalletController;
133 import com.android.wm.shell.animation.FlingAnimationUtils;
134 
135 import org.junit.Before;
136 import org.junit.Test;
137 import org.junit.runner.RunWith;
138 import org.mockito.ArgumentCaptor;
139 import org.mockito.Mock;
140 import org.mockito.MockitoAnnotations;
141 import org.mockito.stubbing.Answer;
142 
143 import java.util.List;
144 import java.util.Optional;
145 
146 @SmallTest
147 @RunWith(AndroidTestingRunner.class)
148 @TestableLooper.RunWithLooper
149 public class NotificationPanelViewControllerTest extends SysuiTestCase {
150 
151     private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
152 
153     @Mock
154     private StatusBar mStatusBar;
155     @Mock
156     private NotificationStackScrollLayout mNotificationStackScrollLayout;
157     @Mock
158     private KeyguardBottomAreaView mKeyguardBottomArea;
159     @Mock
160     private KeyguardBottomAreaView mQsFrame;
161     private KeyguardStatusView mKeyguardStatusView;
162     @Mock
163     private NotificationIconAreaController mNotificationAreaController;
164     @Mock
165     private HeadsUpManagerPhone mHeadsUpManager;
166     @Mock
167     private NotificationShelfController mNotificationShelfController;
168     @Mock
169     private NotificationGroupManagerLegacy mGroupManager;
170     @Mock
171     private KeyguardStatusBarView mKeyguardStatusBar;
172     @Mock
173     private View mUserSwitcherView;
174     @Mock
175     private ViewStub mUserSwitcherStubView;
176     @Mock
177     private HeadsUpTouchHelper.Callback mHeadsUpCallback;
178     @Mock
179     private KeyguardUpdateMonitor mUpdateMonitor;
180     @Mock
181     private KeyguardBypassController mKeyguardBypassController;
182     @Mock
183     private DozeParameters mDozeParameters;
184     @Mock
185     private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
186     @Mock
187     private NotificationPanelView mView;
188     @Mock
189     private LayoutInflater mLayoutInflater;
190     @Mock
191     private DynamicPrivacyController mDynamicPrivacyController;
192     @Mock
193     private ShadeController mShadeController;
194     @Mock
195     private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
196     @Mock
197     private NotificationEntryManager mNotificationEntryManager;
198     @Mock
199     private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
200     @Mock
201     private KeyguardStateController mKeyguardStateController;
202     @Mock
203     private DozeLog mDozeLog;
204     @Mock
205     private CommandQueue mCommandQueue;
206     @Mock
207     private VibratorHelper mVibratorHelper;
208     @Mock
209     private LatencyTracker mLatencyTracker;
210     @Mock
211     private PowerManager mPowerManager;
212     @Mock
213     private AccessibilityManager mAccessibilityManager;
214     @Mock
215     private MetricsLogger mMetricsLogger;
216     @Mock
217     private ActivityManager mActivityManager;
218     @Mock
219     private Resources mResources;
220     @Mock
221     private Configuration mConfiguration;
222     private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
223     @Mock
224     private KeyguardClockSwitch mKeyguardClockSwitch;
225     private PanelViewController.TouchHandler mTouchHandler;
226     private ConfigurationController mConfigurationController;
227     @Mock
228     private MediaHierarchyManager mMediaHiearchyManager;
229     @Mock
230     private ConversationNotificationManager mConversationNotificationManager;
231     @Mock
232     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
233     @Mock
234     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
235     @Mock
236     private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
237     @Mock
238     private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
239     @Mock
240     private QSDetailDisplayer mQSDetailDisplayer;
241     @Mock
242     private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
243     @Mock
244     private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
245     @Mock
246     private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
247     @Mock
248     private KeyguardClockSwitchController mKeyguardClockSwitchController;
249     @Mock
250     private KeyguardStatusViewController mKeyguardStatusViewController;
251     @Mock
252     private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
253     @Mock
254     private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
255     @Mock
256     private NotificationShadeDepthController mNotificationShadeDepthController;
257     @Mock
258     private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
259     @Mock
260     private AuthController mAuthController;
261     @Mock
262     private ScrimController mScrimController;
263     @Mock
264     private MediaDataManager mMediaDataManager;
265     @Mock
266     private AmbientState mAmbientState;
267     @Mock
268     private UserManager mUserManager;
269     @Mock
270     private UiEventLogger mUiEventLogger;
271     @Mock
272     private LockIconViewController mLockIconViewController;
273     @Mock
274     private KeyguardMediaController mKeyguardMediaController;
275     @Mock
276     private PrivacyDotViewController mPrivacyDotViewController;
277     @Mock
278     private NavigationModeController mNavigationModeController;
279     @Mock
280     private SecureSettings mSecureSettings;
281     @Mock
282     private SplitShadeHeaderController mSplitShadeHeaderController;
283     @Mock
284     private ContentResolver mContentResolver;
285     @Mock
286     private TapAgainViewController mTapAgainViewController;
287     @Mock
288     private KeyguardIndicationController mKeyguardIndicationController;
289     @Mock
290     private FragmentService mFragmentService;
291     @Mock
292     private FragmentHostManager mFragmentHostManager;
293     @Mock
294     private QuickAccessWalletController mQuickAccessWalletController;
295     @Mock
296     private NotificationRemoteInputManager mNotificationRemoteInputManager;
297     @Mock
298     private RecordingController mRecordingController;
299     @Mock
300     private ControlsComponent mControlsComponent;
301     @Mock
302     private LockscreenGestureLogger mLockscreenGestureLogger;
303     @Mock
304     private DumpManager mDumpManager;
305     @Mock
306     private NotificationsQSContainerController mNotificationsQSContainerController;
307     @Mock
308     private FeatureFlags mFeatureFlags;
309     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
310     private SysuiStatusBarStateController mStatusBarStateController;
311     private NotificationPanelViewController mNotificationPanelViewController;
312     private View.AccessibilityDelegate mAccessibiltyDelegate;
313     private NotificationsQuickSettingsContainer mNotificationContainerParent;
314     private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
315     private FalsingManagerFake mFalsingManager = new FalsingManagerFake();
316 
317     @Before
setup()318     public void setup() {
319         MockitoAnnotations.initMocks(this);
320         mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager);
321 
322         mKeyguardStatusView = new KeyguardStatusView(mContext);
323         mKeyguardStatusView.setId(R.id.keyguard_status_view);
324 
325         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
326         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
327         when(mView.getResources()).thenReturn(mResources);
328         when(mResources.getConfiguration()).thenReturn(mConfiguration);
329         mConfiguration.orientation = ORIENTATION_PORTRAIT;
330         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
331         mDisplayMetrics.density = 100;
332         when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
333         when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade))
334                 .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE);
335         when(mResources.getDimensionPixelSize(R.dimen.qs_panel_width)).thenReturn(400);
336         when(mResources.getDimensionPixelSize(R.dimen.notification_panel_width)).thenReturn(400);
337         when(mView.getContext()).thenReturn(getContext());
338         when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
339         when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView);
340         when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn(
341                 mUserSwitcherStubView);
342         when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
343         when(mView.findViewById(R.id.notification_stack_scroller))
344                 .thenReturn(mNotificationStackScrollLayout);
345         when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
346         when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
347                 .thenReturn(mHeadsUpCallback);
348         when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
349         when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
350         when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
351         when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
352         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
353         when(mView.findViewById(R.id.keyguard_status_view))
354                 .thenReturn(mock(KeyguardStatusView.class));
355         mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
356         mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
357         mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
358         mNotificationContainerParent.addView(mKeyguardStatusView);
359         mNotificationContainerParent.onFinishInflate();
360         when(mView.findViewById(R.id.notification_container_parent))
361                 .thenReturn(mNotificationContainerParent);
362         when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager);
363         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
364                 mDisplayMetrics);
365 
366         doAnswer((Answer<Void>) invocation -> {
367             mTouchHandler = invocation.getArgument(0);
368             return null;
369         }).when(mView).setOnTouchListener(any(PanelViewController.TouchHandler.class));
370 
371         NotificationWakeUpCoordinator coordinator =
372                 new NotificationWakeUpCoordinator(
373                         mock(HeadsUpManagerPhone.class),
374                         new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager),
375                         mKeyguardBypassController,
376                         mDozeParameters,
377                         mUnlockedScreenOffAnimationController);
378         mConfigurationController = new ConfigurationControllerImpl(mContext);
379         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
380                 mContext,
381                 coordinator,
382                 mKeyguardBypassController, mHeadsUpManager,
383                 mock(NotificationRoundnessManager.class),
384                 mConfigurationController,
385                 mStatusBarStateController,
386                 mFalsingManager,
387                 mLockscreenShadeTransitionController,
388                 new FalsingCollectorFake(),
389                 mDumpManager);
390         when(mKeyguardStatusViewComponentFactory.build(any()))
391                 .thenReturn(mKeyguardStatusViewComponent);
392         when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
393                 .thenReturn(mKeyguardClockSwitchController);
394         when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
395                 .thenReturn(mKeyguardStatusViewController);
396         when(mKeyguardStatusBarViewComponentFactory.build(any(), any()))
397                 .thenReturn(mKeyguardStatusBarViewComponent);
398         when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
399                 .thenReturn(mKeyguardStatusBarViewController);
400         when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean()))
401                 .thenReturn(mKeyguardStatusView);
402         when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean()))
403                 .thenReturn(mKeyguardBottomArea);
404         when(mNotificationRemoteInputManager.isRemoteInputActive()).thenReturn(false);
405 
406         reset(mView);
407 
408         mNotificationPanelViewController = new NotificationPanelViewController(mView,
409                 mResources,
410                 new Handler(Looper.getMainLooper()),
411                 mLayoutInflater,
412                 coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
413                 mFalsingManager, new FalsingCollectorFake(),
414                 mNotificationLockscreenUserManager, mNotificationEntryManager,
415                 mKeyguardStateController, mStatusBarStateController, mDozeLog,
416                 mDozeParameters, mCommandQueue, mVibratorHelper,
417                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
418                 mMetricsLogger, mActivityManager, mConfigurationController,
419                 () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
420                 mConversationNotificationManager, mMediaHiearchyManager,
421                 mStatusBarKeyguardViewManager,
422                 mNotificationsQSContainerController,
423                 mNotificationStackScrollLayoutController,
424                 mKeyguardStatusViewComponentFactory,
425                 mKeyguardQsUserSwitchComponentFactory,
426                 mKeyguardUserSwitcherComponentFactory,
427                 mKeyguardStatusBarViewComponentFactory,
428                 mLockscreenShadeTransitionController,
429                 mQSDetailDisplayer,
430                 mGroupManager,
431                 mNotificationAreaController,
432                 mAuthController,
433                 mScrimController,
434                 mUserManager,
435                 mMediaDataManager,
436                 mNotificationShadeDepthController,
437                 mAmbientState,
438                 mLockIconViewController,
439                 mKeyguardMediaController,
440                 mPrivacyDotViewController,
441                 mTapAgainViewController,
442                 mNavigationModeController,
443                 mFragmentService,
444                 mContentResolver,
445                 mQuickAccessWalletController,
446                 mRecordingController,
447                 new FakeExecutor(new FakeSystemClock()),
448                 mSecureSettings,
449                 mSplitShadeHeaderController,
450                 mUnlockedScreenOffAnimationController,
451                 mLockscreenGestureLogger,
452                 new PanelExpansionStateManager(),
453                 mNotificationRemoteInputManager,
454                 mSysUIUnfoldComponent,
455                 mControlsComponent,
456                 mFeatureFlags);
457         mNotificationPanelViewController.initDependencies(
458                 mStatusBar,
459                 () -> {},
460                 mNotificationShelfController);
461         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
462         mNotificationPanelViewController.setKeyguardIndicationController(
463                 mKeyguardIndicationController);
464         ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
465                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
466         verify(mView, atLeast(1)).addOnAttachStateChangeListener(
467                 onAttachStateChangeListenerArgumentCaptor.capture());
468         mOnAttachStateChangeListeners = onAttachStateChangeListenerArgumentCaptor.getAllValues();
469 
470         ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
471                 ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
472         verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
473         mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
474         mNotificationPanelViewController.mStatusBarStateController
475                 .addCallback(mNotificationPanelViewController.mStatusBarStateListener);
476         mNotificationPanelViewController
477                 .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
478     }
479 
480     @Test
testSetPanelScrimMinFraction()481     public void testSetPanelScrimMinFraction() {
482         mNotificationPanelViewController.setPanelScrimMinFraction(0.5f);
483         verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
484     }
485 
486     @Test
testSetDozing_notifiesNsslAndStateController()487     public void testSetDozing_notifiesNsslAndStateController() {
488         mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */,
489                 null /* touch */);
490         verify(mNotificationStackScrollLayoutController)
491                 .setDozing(eq(true), eq(false), eq(null));
492         assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f);
493     }
494 
495     @Test
testSetExpandedHeight()496     public void testSetExpandedHeight() {
497         mNotificationPanelViewController.setExpandedHeight(200);
498         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
499     }
500 
501     @Test
testOnTouchEvent_expansionCanBeBlocked()502     public void testOnTouchEvent_expansionCanBeBlocked() {
503         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
504                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
505                 0 /* metaState */));
506         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
507                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
508                 0 /* metaState */));
509         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
510         assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
511 
512         mNotificationPanelViewController.blockExpansionForCurrentTouch();
513         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
514                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
515                 0 /* metaState */));
516         // Expansion should not have changed because it was blocked
517         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
518         assertThat(mNotificationPanelViewController.isTrackingBlocked()).isTrue();
519 
520         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
521                 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
522                 0 /* metaState */));
523         assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
524     }
525 
526     @Test
handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent()527     public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
528         when(mCommandQueue.panelsEnabled()).thenReturn(false);
529 
530         boolean returnVal = mNotificationPanelViewController
531                 .getStatusBarTouchEventHandler()
532                 .handleTouchEvent(
533                         MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
534 
535         assertThat(returnVal).isFalse();
536         verify(mView, never()).dispatchTouchEvent(any());
537     }
538 
539     @Test
handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent()540     public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
541         when(mCommandQueue.panelsEnabled()).thenReturn(true);
542         when(mView.isEnabled()).thenReturn(false);
543 
544         boolean returnVal = mNotificationPanelViewController
545                 .getStatusBarTouchEventHandler()
546                 .handleTouchEvent(
547                         MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
548 
549         assertThat(returnVal).isTrue();
550         verify(mView, never()).dispatchTouchEvent(any());
551     }
552 
553     @Test
handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent()554     public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
555         when(mCommandQueue.panelsEnabled()).thenReturn(true);
556         when(mView.isEnabled()).thenReturn(false);
557         MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
558 
559         mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
560 
561         verify(mView).dispatchTouchEvent(event);
562     }
563 
564     @Test
handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent()565     public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
566         when(mCommandQueue.panelsEnabled()).thenReturn(true);
567         when(mView.isEnabled()).thenReturn(true);
568         MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
569 
570         mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
571 
572         verify(mView).dispatchTouchEvent(event);
573     }
574 
575     @Test
testA11y_initializeNode()576     public void testA11y_initializeNode() {
577         AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
578         mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
579 
580         List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
581         assertThat(actionList).containsAtLeastElementsIn(
582                 new AccessibilityNodeInfo.AccessibilityAction[] {
583                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD,
584                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP}
585         );
586     }
587 
588     @Test
testA11y_scrollForward()589     public void testA11y_scrollForward() {
590         mAccessibiltyDelegate.performAccessibilityAction(
591                 mView,
592                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
593                 null);
594 
595         verify(mStatusBarKeyguardViewManager).showBouncer(true);
596     }
597 
598     @Test
testA11y_scrollUp()599     public void testA11y_scrollUp() {
600         mAccessibiltyDelegate.performAccessibilityAction(
601                 mView,
602                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
603                 null);
604 
605         verify(mStatusBarKeyguardViewManager).showBouncer(true);
606     }
607 
608     @Test
testAllChildrenOfNotificationContainer_haveIds()609     public void testAllChildrenOfNotificationContainer_haveIds() {
610         enableSplitShade(/* enabled= */ true);
611         mNotificationContainerParent.removeAllViews();
612         mNotificationContainerParent.addView(newViewWithId(1));
613         mNotificationContainerParent.addView(newViewWithId(View.NO_ID));
614 
615         mNotificationPanelViewController.updateResources();
616 
617         assertThat(mNotificationContainerParent.getChildAt(0).getId()).isEqualTo(1);
618         assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID);
619     }
620 
621     @Test
testSinglePaneShadeLayout_isAlignedToParent()622     public void testSinglePaneShadeLayout_isAlignedToParent() {
623         enableSplitShade(/* enabled= */ false);
624 
625         mNotificationPanelViewController.updateResources();
626 
627         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
628                 .isEqualTo(ConstraintSet.PARENT_ID);
629         assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
630                 .isEqualTo(ConstraintSet.PARENT_ID);
631     }
632 
633     @Test
testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications()634     public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() {
635         mStatusBarStateController.setState(KEYGUARD);
636         enableSplitShade(/* enabled= */ true);
637 
638         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
639         mNotificationPanelViewController.updateResources();
640         assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
641                 .isEqualTo(R.id.qs_edge_guideline);
642 
643         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
644         mNotificationPanelViewController.updateResources();
645         assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
646                 .isEqualTo(ConstraintSet.PARENT_ID);
647     }
648 
649     @Test
testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy()650     public void testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() {
651         givenViewAttached();
652         when(mResources.getBoolean(
653                 com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
654         updateMultiUserSetting(true);
655         clearInvocations(mView);
656 
657         updateMultiUserSetting(false);
658 
659         ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class);
660         verify(mView, atLeastOnce()).addView(captor.capture(), anyInt());
661         final View userSwitcherStub = CollectionUtils.find(captor.getAllValues(),
662                 view -> view.getId() == R.id.keyguard_user_switcher_stub);
663         assertThat(userSwitcherStub).isNotNull();
664         assertThat(userSwitcherStub).isInstanceOf(ViewStub.class);
665     }
666 
667     @Test
testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView()668     public void testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() {
669         givenViewAttached();
670         when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(null);
671         updateSmallestScreenWidth(300);
672         when(mResources.getBoolean(
673                 com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
674         when(mUserManager.isUserSwitcherEnabled()).thenReturn(true);
675 
676         updateSmallestScreenWidth(800);
677 
678         verify(mUserSwitcherStubView).inflate();
679     }
680 
681     @Test
testSplitShadeLayout_isAlignedToGuideline()682     public void testSplitShadeLayout_isAlignedToGuideline() {
683         enableSplitShade(/* enabled= */ true);
684 
685         mNotificationPanelViewController.updateResources();
686 
687         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
688                 .isEqualTo(R.id.qs_edge_guideline);
689         assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
690                 .isEqualTo(R.id.qs_edge_guideline);
691     }
692 
693     @Test
testSinglePaneShadeLayout_childrenHaveConstantWidth()694     public void testSinglePaneShadeLayout_childrenHaveConstantWidth() {
695         enableSplitShade(/* enabled= */ false);
696 
697         mNotificationPanelViewController.updateResources();
698 
699         assertThat(getConstraintSetLayout(R.id.qs_frame).mWidth)
700                 .isEqualTo(mResources.getDimensionPixelSize(R.dimen.qs_panel_width));
701         assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).mWidth)
702                 .isEqualTo(mResources.getDimensionPixelSize(R.dimen.notification_panel_width));
703     }
704 
705     @Test
testSplitShadeLayout_childrenHaveZeroWidth()706     public void testSplitShadeLayout_childrenHaveZeroWidth() {
707         enableSplitShade(/* enabled= */ true);
708 
709         mNotificationPanelViewController.updateResources();
710 
711         assertThat(getConstraintSetLayout(R.id.qs_frame).mWidth).isEqualTo(0);
712         assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).mWidth).isEqualTo(0);
713     }
714 
715     @Test
testCanCollapsePanelOnTouch_trueForKeyGuard()716     public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
717         mStatusBarStateController.setState(KEYGUARD);
718 
719         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
720     }
721 
722     @Test
testCanCollapsePanelOnTouch_trueWhenScrolledToBottom()723     public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() {
724         mStatusBarStateController.setState(SHADE);
725         when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true);
726 
727         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
728     }
729 
730     @Test
testCanCollapsePanelOnTouch_trueWhenInSettings()731     public void testCanCollapsePanelOnTouch_trueWhenInSettings() {
732         mStatusBarStateController.setState(SHADE);
733         mNotificationPanelViewController.setQsExpanded(true);
734 
735         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
736     }
737 
738     @Test
testCanCollapsePanelOnTouch_falseInDualPaneShade()739     public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
740         mStatusBarStateController.setState(SHADE);
741         enableSplitShade(/* enabled= */ true);
742         mNotificationPanelViewController.setQsExpanded(true);
743 
744         assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse();
745     }
746 
747     @Test
testSwipeWhileLocked_notifiesKeyguardState()748     public void testSwipeWhileLocked_notifiesKeyguardState() {
749         mStatusBarStateController.setState(KEYGUARD);
750 
751         // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that
752         // the fling occurred and did not dismiss the keyguard.
753         mNotificationPanelViewController.flingToHeight(
754                 0f, true /* expand */, 1000f, 1f, false);
755         verify(mKeyguardStateController).notifyPanelFlingStart(false /* dismissKeyguard */);
756 
757         // Fling un-expanded, which is a keyguard exit fling when we're in KEYGUARD state.
758         mNotificationPanelViewController.flingToHeight(
759                 0f, false /* expand */, 1000f, 1f, false);
760         verify(mKeyguardStateController).notifyPanelFlingStart(true /* dismissKeyguard */);
761     }
762 
763     @Test
testCancelSwipeWhileLocked_notifiesKeyguardState()764     public void testCancelSwipeWhileLocked_notifiesKeyguardState() {
765         mStatusBarStateController.setState(KEYGUARD);
766 
767         // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that
768         // the fling occurred and did not dismiss the keyguard.
769         mNotificationPanelViewController.flingToHeight(
770                 0f, true /* expand */, 1000f, 1f, false);
771         mNotificationPanelViewController.cancelHeightAnimator();
772         verify(mKeyguardStateController).notifyPanelFlingEnd();
773     }
774 
775     @Test
testDoubleTapRequired_Keyguard()776     public void testDoubleTapRequired_Keyguard() {
777         FalsingManager.FalsingTapListener listener = getFalsingTapListener();
778         mStatusBarStateController.setState(KEYGUARD);
779 
780         listener.onDoubleTapRequired();
781 
782         verify(mKeyguardIndicationController).showTransientIndication(anyInt());
783     }
784 
785     @Test
testDoubleTapRequired_ShadeLocked()786     public void testDoubleTapRequired_ShadeLocked() {
787         FalsingManager.FalsingTapListener listener = getFalsingTapListener();
788         mStatusBarStateController.setState(SHADE_LOCKED);
789 
790         listener.onDoubleTapRequired();
791 
792         verify(mTapAgainViewController).show();
793     }
794 
795     @Test
testSwitchesToCorrectClockInSinglePaneShade()796     public void testSwitchesToCorrectClockInSinglePaneShade() {
797         mStatusBarStateController.setState(KEYGUARD);
798 
799         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
800         triggerPositionClockAndNotifications();
801         verify(mKeyguardStatusViewController).displayClock(LARGE);
802 
803         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
804         mNotificationPanelViewController.closeQs();
805         verify(mKeyguardStatusViewController).displayClock(SMALL);
806     }
807 
808     @Test
testSwitchesToCorrectClockInSplitShade()809     public void testSwitchesToCorrectClockInSplitShade() {
810         mStatusBarStateController.setState(KEYGUARD);
811         enableSplitShade(/* enabled= */ true);
812 
813         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
814         triggerPositionClockAndNotifications();
815         verify(mKeyguardStatusViewController).displayClock(LARGE);
816 
817         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
818         triggerPositionClockAndNotifications();
819         verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE);
820         verify(mKeyguardStatusViewController, never()).displayClock(SMALL);
821     }
822 
823     @Test
testSwitchesToBigClockInSplitShadeOnAod()824     public void testSwitchesToBigClockInSplitShadeOnAod() {
825         mStatusBarStateController.setState(KEYGUARD);
826         enableSplitShade(/* enabled= */ true);
827         when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
828         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
829 
830         mNotificationPanelViewController.setDozing(true, false, null);
831 
832         verify(mKeyguardStatusViewController).displayClock(LARGE);
833     }
834 
835     @Test
testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying()836     public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() {
837         mStatusBarStateController.setState(KEYGUARD);
838         enableSplitShade(/* enabled= */ true);
839         when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
840 
841         // one notification + media player visible
842         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
843         triggerPositionClockAndNotifications();
844         verify(mKeyguardStatusViewController).displayClock(SMALL);
845 
846         // only media player visible
847         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
848         triggerPositionClockAndNotifications();
849         verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL);
850         verify(mKeyguardStatusViewController, never()).displayClock(LARGE);
851     }
852 
triggerPositionClockAndNotifications()853     private void triggerPositionClockAndNotifications() {
854         mNotificationPanelViewController.closeQs();
855     }
856 
getFalsingTapListener()857     private FalsingManager.FalsingTapListener getFalsingTapListener() {
858         for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
859             listener.onViewAttachedToWindow(mView);
860         }
861         assertThat(mFalsingManager.getTapListeners().size()).isEqualTo(1);
862         return mFalsingManager.getTapListeners().get(0);
863     }
864 
givenViewAttached()865     private void givenViewAttached() {
866         for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
867             listener.onViewAttachedToWindow(mView);
868         }
869     }
870 
newViewWithId(int id)871     private View newViewWithId(int id) {
872         View view = new View(mContext);
873         view.setId(id);
874         ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
875                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
876         // required as cloning ConstraintSet fails if view doesn't have layout params
877         view.setLayoutParams(layoutParams);
878         return view;
879     }
880 
getConstraintSetLayout(@dRes int id)881     private ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) {
882         ConstraintSet constraintSet = new ConstraintSet();
883         constraintSet.clone(mNotificationContainerParent);
884         return constraintSet.getConstraint(id).layout;
885     }
886 
enableSplitShade(boolean enabled)887     private void enableSplitShade(boolean enabled) {
888         when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled);
889         mNotificationPanelViewController.updateResources();
890     }
891 
updateMultiUserSetting(boolean enabled)892     private void updateMultiUserSetting(boolean enabled) {
893         when(mUserManager.isUserSwitcherEnabled()).thenReturn(enabled);
894         final ArgumentCaptor<ContentObserver> observerCaptor =
895                 ArgumentCaptor.forClass(ContentObserver.class);
896         verify(mContentResolver)
897                 .registerContentObserver(any(), anyBoolean(), observerCaptor.capture());
898         observerCaptor.getValue().onChange(/* selfChange */ false);
899     }
900 
updateSmallestScreenWidth(int smallestScreenWidthDp)901     private void updateSmallestScreenWidth(int smallestScreenWidthDp) {
902         Configuration configuration = new Configuration();
903         configuration.smallestScreenWidthDp = smallestScreenWidthDp;
904         mConfigurationController.onConfigurationChanged(configuration);
905     }
906 
onTouchEvent(MotionEvent ev)907     private void onTouchEvent(MotionEvent ev) {
908         mTouchHandler.onTouch(mView, ev);
909     }
910 }
911