| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.shade; |
| |
| import static android.content.res.Configuration.ORIENTATION_PORTRAIT; |
| |
| import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; |
| import static com.android.keyguard.KeyguardClockSwitch.LARGE; |
| import static com.android.keyguard.KeyguardClockSwitch.SMALL; |
| import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; |
| import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; |
| import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING; |
| import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; |
| import static com.android.systemui.statusbar.StatusBarState.SHADE; |
| import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyFloat; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.atLeast; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.clearInvocations; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.inOrder; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.annotation.IdRes; |
| import android.content.ContentResolver; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.PowerManager; |
| import android.os.UserManager; |
| import android.testing.AndroidTestingRunner; |
| import android.testing.TestableLooper; |
| import android.util.DisplayMetrics; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewParent; |
| import android.view.ViewPropertyAnimator; |
| import android.view.ViewStub; |
| import android.view.ViewTreeObserver; |
| import android.view.accessibility.AccessibilityManager; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| |
| import androidx.constraintlayout.widget.ConstraintSet; |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.internal.jank.InteractionJankMonitor; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.UiEventLogger; |
| import com.android.internal.logging.testing.UiEventLoggerFake; |
| import com.android.internal.util.CollectionUtils; |
| import com.android.internal.util.LatencyTracker; |
| import com.android.keyguard.FaceAuthApiRequestReason; |
| import com.android.keyguard.KeyguardClockSwitch; |
| import com.android.keyguard.KeyguardClockSwitchController; |
| import com.android.keyguard.KeyguardStatusView; |
| import com.android.keyguard.KeyguardStatusViewController; |
| import com.android.keyguard.KeyguardUpdateMonitor; |
| import com.android.keyguard.LockIconViewController; |
| import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; |
| import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; |
| import com.android.keyguard.dagger.KeyguardStatusViewComponent; |
| import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; |
| import com.android.systemui.DejankUtils; |
| import com.android.systemui.R; |
| import com.android.systemui.SysuiTestCase; |
| import com.android.systemui.biometrics.AuthController; |
| import com.android.systemui.camera.CameraGestureHelper; |
| import com.android.systemui.classifier.FalsingCollectorFake; |
| import com.android.systemui.classifier.FalsingManagerFake; |
| import com.android.systemui.doze.DozeLog; |
| import com.android.systemui.dump.DumpManager; |
| import com.android.systemui.flags.FeatureFlags; |
| import com.android.systemui.fragments.FragmentHostManager; |
| import com.android.systemui.fragments.FragmentService; |
| import com.android.systemui.keyguard.KeyguardUnlockAnimationController; |
| import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; |
| import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; |
| import com.android.systemui.media.controls.pipeline.MediaDataManager; |
| import com.android.systemui.media.controls.ui.KeyguardMediaController; |
| import com.android.systemui.media.controls.ui.MediaHierarchyManager; |
| import com.android.systemui.model.SysUiState; |
| import com.android.systemui.navigationbar.NavigationBarController; |
| import com.android.systemui.navigationbar.NavigationModeController; |
| import com.android.systemui.plugins.FalsingManager; |
| import com.android.systemui.plugins.qs.QS; |
| import com.android.systemui.plugins.statusbar.StatusBarStateController; |
| import com.android.systemui.qs.QSFragment; |
| import com.android.systemui.screenrecord.RecordingController; |
| import com.android.systemui.shade.transition.ShadeTransitionController; |
| import com.android.systemui.statusbar.CommandQueue; |
| import com.android.systemui.statusbar.KeyguardIndicationController; |
| import com.android.systemui.statusbar.LockscreenShadeTransitionController; |
| import com.android.systemui.statusbar.NotificationRemoteInputManager; |
| import com.android.systemui.statusbar.NotificationShadeDepthController; |
| import com.android.systemui.statusbar.NotificationShadeWindowController; |
| import com.android.systemui.statusbar.NotificationShelfController; |
| import com.android.systemui.statusbar.PulseExpansionHandler; |
| import com.android.systemui.statusbar.QsFrameTranslateController; |
| import com.android.systemui.statusbar.StatusBarStateControllerImpl; |
| import com.android.systemui.statusbar.SysuiStatusBarStateController; |
| import com.android.systemui.statusbar.VibratorHelper; |
| import com.android.systemui.statusbar.events.PrivacyDotViewController; |
| import com.android.systemui.statusbar.notification.ConversationNotificationManager; |
| import com.android.systemui.statusbar.notification.DynamicPrivacyController; |
| import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; |
| import com.android.systemui.statusbar.notification.row.ExpandableView; |
| import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; |
| import com.android.systemui.statusbar.notification.stack.AmbientState; |
| import com.android.systemui.statusbar.notification.stack.NotificationListContainer; |
| import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; |
| import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; |
| import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; |
| import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; |
| import com.android.systemui.statusbar.phone.CentralSurfaces; |
| import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; |
| import com.android.systemui.statusbar.phone.DozeParameters; |
| import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; |
| import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; |
| import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; |
| import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; |
| import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; |
| import com.android.systemui.statusbar.phone.KeyguardBypassController; |
| import com.android.systemui.statusbar.phone.KeyguardStatusBarView; |
| import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; |
| import com.android.systemui.statusbar.phone.LockscreenGestureLogger; |
| import com.android.systemui.statusbar.phone.NotificationIconAreaController; |
| import com.android.systemui.statusbar.phone.ScreenOffAnimationController; |
| import com.android.systemui.statusbar.phone.ScrimController; |
| import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; |
| import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; |
| import com.android.systemui.statusbar.phone.TapAgainViewController; |
| import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; |
| import com.android.systemui.statusbar.policy.ConfigurationController; |
| import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; |
| import com.android.systemui.statusbar.policy.KeyguardStateController; |
| import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; |
| import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; |
| import com.android.systemui.statusbar.window.StatusBarWindowStateController; |
| import com.android.systemui.unfold.SysUIUnfoldComponent; |
| import com.android.systemui.util.time.FakeSystemClock; |
| import com.android.systemui.util.time.SystemClock; |
| import com.android.wm.shell.animation.FlingAnimationUtils; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.stubbing.Answer; |
| |
| import java.util.List; |
| import java.util.Optional; |
| |
| @SmallTest |
| @RunWith(AndroidTestingRunner.class) |
| @TestableLooper.RunWithLooper |
| public class NotificationPanelViewControllerTest extends SysuiTestCase { |
| |
| private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400; |
| private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50; |
| private static final int PANEL_WIDTH = 500; // Random value just for the test. |
| |
| @Mock private CentralSurfaces mCentralSurfaces; |
| @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; |
| @Mock private KeyguardBottomAreaView mKeyguardBottomArea; |
| @Mock private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController; |
| @Mock private KeyguardBottomAreaView mQsFrame; |
| @Mock private NotificationIconAreaController mNotificationAreaController; |
| @Mock private HeadsUpManagerPhone mHeadsUpManager; |
| @Mock private NotificationShelfController mNotificationShelfController; |
| @Mock private KeyguardStatusBarView mKeyguardStatusBar; |
| @Mock private KeyguardUserSwitcherView mUserSwitcherView; |
| @Mock private ViewStub mUserSwitcherStubView; |
| @Mock private HeadsUpTouchHelper.Callback mHeadsUpCallback; |
| @Mock private KeyguardUpdateMonitor mUpdateMonitor; |
| @Mock private KeyguardBypassController mKeyguardBypassController; |
| @Mock private DozeParameters mDozeParameters; |
| @Mock private ScreenOffAnimationController mScreenOffAnimationController; |
| @Mock private NotificationPanelView mView; |
| @Mock private LayoutInflater mLayoutInflater; |
| @Mock private FeatureFlags mFeatureFlags; |
| @Mock private DynamicPrivacyController mDynamicPrivacyController; |
| @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; |
| @Mock private KeyguardStateController mKeyguardStateController; |
| @Mock private DozeLog mDozeLog; |
| @Mock private ShadeLogger mShadeLog; |
| @Mock private CommandQueue mCommandQueue; |
| @Mock private VibratorHelper mVibratorHelper; |
| @Mock private LatencyTracker mLatencyTracker; |
| @Mock private PowerManager mPowerManager; |
| @Mock private AccessibilityManager mAccessibilityManager; |
| @Mock private MetricsLogger mMetricsLogger; |
| @Mock private Resources mResources; |
| @Mock private Configuration mConfiguration; |
| @Mock private KeyguardClockSwitch mKeyguardClockSwitch; |
| @Mock private MediaHierarchyManager mMediaHiearchyManager; |
| @Mock private ConversationNotificationManager mConversationNotificationManager; |
| @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; |
| @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; |
| @Mock private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; |
| @Mock private KeyguardQsUserSwitchComponent mKeyguardQsUserSwitchComponent; |
| @Mock private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; |
| @Mock private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; |
| @Mock private KeyguardUserSwitcherComponent mKeyguardUserSwitcherComponent; |
| @Mock private KeyguardUserSwitcherController mKeyguardUserSwitcherController; |
| @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; |
| @Mock private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; |
| @Mock private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; |
| @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; |
| @Mock private KeyguardStatusViewController mKeyguardStatusViewController; |
| @Mock private KeyguardStatusBarViewController mKeyguardStatusBarViewController; |
| @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; |
| @Mock private NotificationShadeDepthController mNotificationShadeDepthController; |
| @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; |
| @Mock private AuthController mAuthController; |
| @Mock private ScrimController mScrimController; |
| @Mock private MediaDataManager mMediaDataManager; |
| @Mock private AmbientState mAmbientState; |
| @Mock private UserManager mUserManager; |
| @Mock private UiEventLogger mUiEventLogger; |
| @Mock private LockIconViewController mLockIconViewController; |
| @Mock private KeyguardMediaController mKeyguardMediaController; |
| @Mock private PrivacyDotViewController mPrivacyDotViewController; |
| @Mock private NavigationModeController mNavigationModeController; |
| @Mock private NavigationBarController mNavigationBarController; |
| @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; |
| @Mock private ContentResolver mContentResolver; |
| @Mock private TapAgainViewController mTapAgainViewController; |
| @Mock private KeyguardIndicationController mKeyguardIndicationController; |
| @Mock private FragmentService mFragmentService; |
| @Mock private FragmentHostManager mFragmentHostManager; |
| @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager; |
| @Mock private RecordingController mRecordingController; |
| @Mock private LockscreenGestureLogger mLockscreenGestureLogger; |
| @Mock private DumpManager mDumpManager; |
| @Mock private InteractionJankMonitor mInteractionJankMonitor; |
| @Mock private NotificationsQSContainerController mNotificationsQSContainerController; |
| @Mock private QsFrameTranslateController mQsFrameTranslateController; |
| @Mock private StatusBarWindowStateController mStatusBarWindowStateController; |
| @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; |
| @Mock private NotificationShadeWindowController mNotificationShadeWindowController; |
| @Mock private SysUiState mSysUiState; |
| @Mock private NotificationListContainer mNotificationListContainer; |
| @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; |
| @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; |
| @Mock private ShadeTransitionController mShadeTransitionController; |
| @Mock private QS mQs; |
| @Mock private QSFragment mQSFragment; |
| @Mock private ViewGroup mQsHeader; |
| @Mock private ViewParent mViewParent; |
| @Mock private ViewTreeObserver mViewTreeObserver; |
| @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; |
| @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; |
| @Mock private MotionEvent mDownMotionEvent; |
| @Captor |
| private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> |
| mEmptySpaceClickListenerCaptor; |
| |
| private NotificationPanelViewController.TouchHandler mTouchHandler; |
| private ConfigurationController mConfigurationController; |
| private SysuiStatusBarStateController mStatusBarStateController; |
| private NotificationPanelViewController mNotificationPanelViewController; |
| private View.AccessibilityDelegate mAccessibiltyDelegate; |
| private NotificationsQuickSettingsContainer mNotificationContainerParent; |
| private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; |
| private Handler mMainHandler; |
| private View.OnLayoutChangeListener mLayoutChangeListener; |
| |
| private final FalsingManagerFake mFalsingManager = new FalsingManagerFake(); |
| private final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); |
| private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); |
| private final ShadeExpansionStateManager mShadeExpansionStateManager = |
| new ShadeExpansionStateManager(); |
| private FragmentHostManager.FragmentListener mFragmentListener; |
| |
| @Before |
| public void setup() { |
| MockitoAnnotations.initMocks(this); |
| SystemClock systemClock = new FakeSystemClock(); |
| mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, |
| mInteractionJankMonitor); |
| |
| KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); |
| keyguardStatusView.setId(R.id.keyguard_status_view); |
| DejankUtils.setImmediate(true); |
| |
| when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); |
| when(mHeadsUpCallback.getContext()).thenReturn(mContext); |
| when(mView.getResources()).thenReturn(mResources); |
| when(mView.getWidth()).thenReturn(PANEL_WIDTH); |
| when(mResources.getConfiguration()).thenReturn(mConfiguration); |
| mConfiguration.orientation = ORIENTATION_PORTRAIT; |
| when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); |
| mDisplayMetrics.density = 100; |
| when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true); |
| when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade)) |
| .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE); |
| when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)) |
| .thenReturn(10); |
| when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance)) |
| .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); |
| when(mView.getContext()).thenReturn(getContext()); |
| when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); |
| when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView); |
| when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn( |
| mUserSwitcherStubView); |
| when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch); |
| when(mView.findViewById(R.id.notification_stack_scroller)) |
| .thenReturn(mNotificationStackScrollLayout); |
| when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000); |
| when(mNotificationStackScrollLayoutController.getHeadsUpCallback()) |
| .thenReturn(mHeadsUpCallback); |
| when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea); |
| when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea); |
| when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class)); |
| when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); |
| when(mView.findViewById(R.id.keyguard_status_view)) |
| .thenReturn(mock(KeyguardStatusView.class)); |
| mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null); |
| mNotificationContainerParent.addView(keyguardStatusView); |
| mNotificationContainerParent.onFinishInflate(); |
| when(mView.findViewById(R.id.notification_container_parent)) |
| .thenReturn(mNotificationContainerParent); |
| when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager); |
| FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder( |
| mDisplayMetrics); |
| when(mKeyguardQsUserSwitchComponentFactory.build(any())) |
| .thenReturn(mKeyguardQsUserSwitchComponent); |
| when(mKeyguardQsUserSwitchComponent.getKeyguardQsUserSwitchController()) |
| .thenReturn(mKeyguardQsUserSwitchController); |
| when(mKeyguardUserSwitcherComponentFactory.build(any())) |
| .thenReturn(mKeyguardUserSwitcherComponent); |
| when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController()) |
| .thenReturn(mKeyguardUserSwitcherController); |
| when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true); |
| when(mQs.getView()).thenReturn(mView); |
| when(mQSFragment.getView()).thenReturn(mView); |
| doAnswer(invocation -> { |
| mFragmentListener = invocation.getArgument(1); |
| return null; |
| }).when(mFragmentHostManager).addTagListener(eq(QS.TAG), any()); |
| doAnswer((Answer<Void>) invocation -> { |
| mTouchHandler = invocation.getArgument(0); |
| return null; |
| }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class)); |
| |
| NotificationWakeUpCoordinator coordinator = |
| new NotificationWakeUpCoordinator( |
| mDumpManager, |
| mock(HeadsUpManagerPhone.class), |
| new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, |
| mInteractionJankMonitor), |
| mKeyguardBypassController, |
| mDozeParameters, |
| mScreenOffAnimationController); |
| mConfigurationController = new ConfigurationControllerImpl(mContext); |
| PulseExpansionHandler expansionHandler = new PulseExpansionHandler( |
| mContext, |
| coordinator, |
| mKeyguardBypassController, mHeadsUpManager, |
| mock(NotificationRoundnessManager.class), |
| mConfigurationController, |
| mStatusBarStateController, |
| mFalsingManager, |
| mShadeExpansionStateManager, |
| mLockscreenShadeTransitionController, |
| new FalsingCollectorFake(), |
| mDumpManager); |
| when(mKeyguardStatusViewComponentFactory.build(any())) |
| .thenReturn(mKeyguardStatusViewComponent); |
| when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController()) |
| .thenReturn(mKeyguardClockSwitchController); |
| when(mKeyguardStatusViewComponent.getKeyguardStatusViewController()) |
| .thenReturn(mKeyguardStatusViewController); |
| when(mKeyguardStatusBarViewComponentFactory.build(any(), any())) |
| .thenReturn(mKeyguardStatusBarViewComponent); |
| when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController()) |
| .thenReturn(mKeyguardStatusBarViewController); |
| when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean())) |
| .thenReturn(keyguardStatusView); |
| when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean())) |
| .thenReturn(mUserSwitcherView); |
| when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean())) |
| .thenReturn(mKeyguardBottomArea); |
| when(mNotificationRemoteInputManager.isRemoteInputActive()) |
| .thenReturn(false); |
| when(mInteractionJankMonitor.begin(any(), anyInt())) |
| .thenReturn(true); |
| when(mInteractionJankMonitor.end(anyInt())) |
| .thenReturn(true); |
| doAnswer(invocation -> { |
| ((Runnable) invocation.getArgument(0)).run(); |
| return null; |
| }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); |
| doAnswer(invocation -> { |
| mLayoutChangeListener = invocation.getArgument(0); |
| return null; |
| }).when(mView).addOnLayoutChangeListener(any()); |
| |
| when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver); |
| when(mView.getParent()).thenReturn(mViewParent); |
| when(mQs.getHeader()).thenReturn(mQsHeader); |
| when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); |
| when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); |
| |
| mMainHandler = new Handler(Looper.getMainLooper()); |
| NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter = |
| new NotificationPanelViewController.PanelEventsEmitter(); |
| |
| mNotificationPanelViewController = new NotificationPanelViewController( |
| mView, |
| mMainHandler, |
| mLayoutInflater, |
| mFeatureFlags, |
| coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController, |
| mFalsingManager, new FalsingCollectorFake(), |
| mKeyguardStateController, |
| mStatusBarStateController, |
| mStatusBarWindowStateController, |
| mNotificationShadeWindowController, |
| mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, |
| mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor, |
| mMetricsLogger, |
| mShadeLog, |
| mConfigurationController, |
| () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, |
| mConversationNotificationManager, mMediaHiearchyManager, |
| mStatusBarKeyguardViewManager, |
| mNotificationsQSContainerController, |
| mNotificationStackScrollLayoutController, |
| mKeyguardStatusViewComponentFactory, |
| mKeyguardQsUserSwitchComponentFactory, |
| mKeyguardUserSwitcherComponentFactory, |
| mKeyguardStatusBarViewComponentFactory, |
| mLockscreenShadeTransitionController, |
| mNotificationAreaController, |
| mAuthController, |
| mScrimController, |
| mUserManager, |
| mMediaDataManager, |
| mNotificationShadeDepthController, |
| mAmbientState, |
| mLockIconViewController, |
| mKeyguardMediaController, |
| mPrivacyDotViewController, |
| mTapAgainViewController, |
| mNavigationModeController, |
| mNavigationBarController, |
| mFragmentService, |
| mContentResolver, |
| mRecordingController, |
| mLargeScreenShadeHeaderController, |
| mScreenOffAnimationController, |
| mLockscreenGestureLogger, |
| mShadeExpansionStateManager, |
| mNotificationRemoteInputManager, |
| mSysUIUnfoldComponent, |
| mInteractionJankMonitor, |
| mQsFrameTranslateController, |
| mSysUiState, |
| () -> mKeyguardBottomAreaViewController, |
| mKeyguardUnlockAnimationController, |
| mNotificationListContainer, |
| panelEventsEmitter, |
| mNotificationStackSizeCalculator, |
| mUnlockedScreenOffAnimationController, |
| mShadeTransitionController, |
| systemClock, |
| mock(CameraGestureHelper.class), |
| mKeyguardBottomAreaViewModel, |
| mKeyguardBottomAreaInteractor); |
| mNotificationPanelViewController.initDependencies( |
| mCentralSurfaces, |
| () -> {}, |
| mNotificationShelfController); |
| mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); |
| mNotificationPanelViewController.setKeyguardIndicationController( |
| mKeyguardIndicationController); |
| ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor = |
| ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); |
| verify(mView, atLeast(1)).addOnAttachStateChangeListener( |
| onAttachStateChangeListenerArgumentCaptor.capture()); |
| mOnAttachStateChangeListeners = onAttachStateChangeListenerArgumentCaptor.getAllValues(); |
| |
| ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor = |
| ArgumentCaptor.forClass(View.AccessibilityDelegate.class); |
| verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture()); |
| mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue(); |
| mNotificationPanelViewController.getStatusBarStateController() |
| .addCallback(mNotificationPanelViewController.mStatusBarStateListener); |
| mNotificationPanelViewController |
| .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); |
| verify(mNotificationStackScrollLayoutController) |
| .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); |
| } |
| |
| @After |
| public void tearDown() { |
| mNotificationPanelViewController.cancelHeightAnimator(); |
| mMainHandler.removeCallbacksAndMessages(null); |
| } |
| |
| @Test |
| public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() { |
| mStatusBarStateController.setState(KEYGUARD); |
| ArgumentCaptor<OnHeightChangedListener> captor = |
| ArgumentCaptor.forClass(OnHeightChangedListener.class); |
| verify(mNotificationStackScrollLayoutController) |
| .setOnHeightChangedListener(captor.capture()); |
| OnHeightChangedListener listener = captor.getValue(); |
| |
| clearInvocations(mNotificationStackSizeCalculator); |
| listener.onHeightChanged(mock(ExpandableView.class), false); |
| |
| verify(mNotificationStackSizeCalculator) |
| .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat()); |
| } |
| |
| @Test |
| public void onNotificationHeightChangeWhileInShadeWillNotComputeMaxKeyguardNotifications() { |
| mStatusBarStateController.setState(SHADE); |
| ArgumentCaptor<OnHeightChangedListener> captor = |
| ArgumentCaptor.forClass(OnHeightChangedListener.class); |
| verify(mNotificationStackScrollLayoutController) |
| .setOnHeightChangedListener(captor.capture()); |
| OnHeightChangedListener listener = captor.getValue(); |
| |
| clearInvocations(mNotificationStackSizeCalculator); |
| listener.onHeightChanged(mock(ExpandableView.class), false); |
| |
| verify(mNotificationStackSizeCalculator, never()) |
| .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat()); |
| } |
| |
| @Test |
| public void computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax() { |
| when(mAmbientState.getFractionToShade()).thenReturn(0.5f); |
| mNotificationPanelViewController.setMaxDisplayedNotifications(-1); |
| |
| // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value |
| assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications()) |
| .isEqualTo(-1); |
| } |
| |
| @Test |
| public void computeMaxKeyguardNotifications_noTransition_updatesMax() { |
| when(mAmbientState.getFractionToShade()).thenReturn(0f); |
| mNotificationPanelViewController.setMaxDisplayedNotifications(-1); |
| |
| // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value |
| assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications()) |
| .isNotEqualTo(-1); |
| } |
| |
| private void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding, |
| int ambientPadding) { |
| |
| when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); |
| when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom); |
| when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom); |
| when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding)); |
| |
| when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) |
| .thenReturn(indicationPadding); |
| mNotificationPanelViewController.loadDimens(); |
| |
| mNotificationPanelViewController.setAmbientIndicationTop( |
| /* ambientIndicationTop= */ stackBottom - ambientPadding, |
| /* ambientTextVisible= */ true); |
| } |
| |
| @Test |
| public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() { |
| setBottomPadding(/* stackScrollLayoutBottom= */ 100, |
| /* lockIconPadding= */ 20, |
| /* indicationPadding= */ 0, |
| /* ambientPadding= */ 0); |
| |
| assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications()) |
| .isEqualTo(80); |
| } |
| |
| @Test |
| public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() { |
| setBottomPadding(/* stackScrollLayoutBottom= */ 100, |
| /* lockIconPadding= */ 0, |
| /* indicationPadding= */ 30, |
| /* ambientPadding= */ 0); |
| |
| assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications()) |
| .isEqualTo(70); |
| } |
| |
| @Test |
| public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() { |
| setBottomPadding(/* stackScrollLayoutBottom= */ 100, |
| /* lockIconPadding= */ 0, |
| /* indicationPadding= */ 0, |
| /* ambientPadding= */ 40); |
| |
| assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications()) |
| .isEqualTo(60); |
| } |
| |
| @Test |
| public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() { |
| setBottomPadding(/* stackScrollLayoutBottom= */ 100, |
| /* lockIconPadding= */ 20, |
| /* indicationPadding= */ 0, |
| /* ambientPadding= */ 0); |
| |
| when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); |
| assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) |
| .isEqualTo(5); |
| } |
| |
| @Test |
| public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() { |
| setBottomPadding(/* stackScrollLayoutBottom= */ 100, |
| /* lockIconPadding= */ 0, |
| /* indicationPadding= */ 30, |
| /* ambientPadding= */ 0); |
| |
| when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); |
| assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) |
| .isEqualTo(0); |
| } |
| |
| @Test |
| public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() { |
| setBottomPadding(/* stackScrollLayoutBottom= */ 100, |
| /* lockIconPadding= */ 0, |
| /* indicationPadding= */ 0, |
| /* ambientPadding= */ 40); |
| |
| when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); |
| assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) |
| .isEqualTo(0); |
| } |
| |
| @Test |
| public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() { |
| setBottomPadding(/* stackScrollLayoutBottom= */ 100, |
| /* lockIconPadding= */ 10, |
| /* indicationPadding= */ 8, |
| /* ambientPadding= */ 0); |
| |
| when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); |
| assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) |
| .isEqualTo(2); |
| } |
| |
| @Test |
| public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() { |
| mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( |
| mNotificationPanelViewController.getMaxPanelHeight() / 2); |
| verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f)); |
| } |
| |
| @Test |
| public void testSetDozing_notifiesNsslAndStateController() { |
| mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */); |
| verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false)); |
| assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f); |
| } |
| |
| @Test |
| public void testSetExpandedHeight() { |
| mNotificationPanelViewController.setExpandedHeight(200); |
| assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200); |
| } |
| |
| @Test |
| public void testOnTouchEvent_expansionCanBeBlocked() { |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, |
| 0 /* metaState */)); |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, |
| 0 /* metaState */)); |
| assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200); |
| assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse(); |
| |
| mNotificationPanelViewController.blockExpansionForCurrentTouch(); |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */, |
| 0 /* metaState */)); |
| // Expansion should not have changed because it was blocked |
| assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200); |
| assertThat(mNotificationPanelViewController.isTrackingBlocked()).isTrue(); |
| |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */, |
| 0 /* metaState */)); |
| assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse(); |
| } |
| |
| @Test |
| public void test_pulsing_onTouchEvent_noTracking() { |
| // GIVEN device is pulsing |
| mNotificationPanelViewController.setPulsing(true); |
| |
| // WHEN touch DOWN & MOVE events received |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, |
| 0 /* metaState */)); |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, |
| 0 /* metaState */)); |
| |
| // THEN touch is NOT tracked (since the device is pulsing) |
| assertThat(mNotificationPanelViewController.isTracking()).isFalse(); |
| } |
| |
| @Test |
| public void test_onTouchEvent_startTracking() { |
| // GIVEN device is NOT pulsing |
| mNotificationPanelViewController.setPulsing(false); |
| |
| // WHEN touch DOWN & MOVE events received |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, |
| 0 /* metaState */)); |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, |
| 0 /* metaState */)); |
| |
| // THEN touch is tracked |
| assertThat(mNotificationPanelViewController.isTracking()).isTrue(); |
| } |
| |
| @Test |
| public void testOnTouchEvent_expansionResumesAfterBriefTouch() { |
| // Start shade collapse with swipe up |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, |
| 0 /* metaState */)); |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */, |
| 0 /* metaState */)); |
| onTouchEvent(MotionEvent.obtain(0L /* downTime */, |
| 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */, |
| 0 /* metaState */)); |
| |
| assertThat(mNotificationPanelViewController.getClosing()).isTrue(); |
| assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue(); |
| |
| // simulate touch that does not exceed touch slop |
| onTouchEvent(MotionEvent.obtain(2L /* downTime */, |
| 2L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 300f /* y */, |
| 0 /* metaState */)); |
| |
| mNotificationPanelViewController.setTouchSlopExceeded(false); |
| |
| onTouchEvent(MotionEvent.obtain(2L /* downTime */, |
| 2L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */, |
| 0 /* metaState */)); |
| |
| // fling should still be called after a touch that does not exceed touch slop |
| assertThat(mNotificationPanelViewController.getClosing()).isTrue(); |
| assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue(); |
| } |
| |
| @Test |
| public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { |
| when(mCommandQueue.panelsEnabled()).thenReturn(false); |
| |
| boolean returnVal = mNotificationPanelViewController |
| .getStatusBarTouchEventHandler() |
| .handleTouchEvent( |
| MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); |
| |
| assertThat(returnVal).isFalse(); |
| verify(mView, never()).dispatchTouchEvent(any()); |
| } |
| |
| @Test |
| public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() { |
| when(mCommandQueue.panelsEnabled()).thenReturn(true); |
| when(mView.isEnabled()).thenReturn(false); |
| |
| boolean returnVal = mNotificationPanelViewController |
| .getStatusBarTouchEventHandler() |
| .handleTouchEvent( |
| MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); |
| |
| assertThat(returnVal).isTrue(); |
| verify(mView, never()).dispatchTouchEvent(any()); |
| } |
| |
| @Test |
| public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { |
| when(mCommandQueue.panelsEnabled()).thenReturn(true); |
| when(mView.isEnabled()).thenReturn(false); |
| MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0); |
| |
| mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); |
| |
| verify(mView).dispatchTouchEvent(event); |
| } |
| |
| @Test |
| public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { |
| when(mCommandQueue.panelsEnabled()).thenReturn(true); |
| when(mView.isEnabled()).thenReturn(true); |
| MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0); |
| |
| mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); |
| |
| verify(mView).dispatchTouchEvent(event); |
| } |
| |
| @Test |
| public void testA11y_initializeNode() { |
| AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); |
| mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); |
| |
| List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList(); |
| assertThat(actionList).containsAtLeastElementsIn( |
| new AccessibilityNodeInfo.AccessibilityAction[] { |
| AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD, |
| AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP} |
| ); |
| } |
| |
| @Test |
| public void testA11y_scrollForward() { |
| mAccessibiltyDelegate.performAccessibilityAction( |
| mView, |
| AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(), |
| null); |
| |
| verify(mStatusBarKeyguardViewManager).showBouncer(true); |
| } |
| |
| @Test |
| public void testA11y_scrollUp() { |
| mAccessibiltyDelegate.performAccessibilityAction( |
| mView, |
| AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(), |
| null); |
| |
| verify(mStatusBarKeyguardViewManager).showBouncer(true); |
| } |
| |
| @Test |
| public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() { |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| mNotificationPanelViewController.updateResources(); |
| assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd) |
| .isEqualTo(R.id.qs_edge_guideline); |
| |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); |
| mNotificationPanelViewController.updateResources(); |
| assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd) |
| .isEqualTo(ConstraintSet.PARENT_ID); |
| } |
| |
| @Test |
| public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() { |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| |
| setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ true); |
| |
| assertKeyguardStatusViewCentered(); |
| } |
| |
| @Test |
| public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() { |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| |
| setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ false); |
| |
| assertKeyguardStatusViewNotCentered(); |
| } |
| |
| @Test |
| public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() { |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| |
| setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true); |
| |
| assertKeyguardStatusViewNotCentered(); |
| } |
| |
| @Test |
| public void keyguardStatusView_splitShade_pulsing_isNotCentered() { |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(true); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| |
| setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false); |
| |
| assertKeyguardStatusViewNotCentered(); |
| } |
| |
| @Test |
| public void keyguardStatusView_splitShade_notPulsing_isNotCentered() { |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| |
| setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false); |
| |
| assertKeyguardStatusViewNotCentered(); |
| } |
| |
| @Test |
| public void keyguardStatusView_singleShade_isCentered() { |
| enableSplitShade(/* enabled= */ false); |
| // The conditions below would make the clock NOT be centered on split shade. |
| // On single shade it should always be centered though. |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false); |
| mStatusBarStateController.setState(KEYGUARD); |
| setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false); |
| |
| assertKeyguardStatusViewCentered(); |
| } |
| |
| @Test |
| public void testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() { |
| givenViewAttached(); |
| when(mResources.getBoolean( |
| com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); |
| updateMultiUserSetting(true); |
| clearInvocations(mView); |
| |
| updateMultiUserSetting(false); |
| |
| ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class); |
| verify(mView, atLeastOnce()).addView(captor.capture(), anyInt()); |
| final View userSwitcherStub = CollectionUtils.find(captor.getAllValues(), |
| view -> view.getId() == R.id.keyguard_user_switcher_stub); |
| assertThat(userSwitcherStub).isNotNull(); |
| assertThat(userSwitcherStub).isInstanceOf(ViewStub.class); |
| } |
| |
| @Test |
| public void testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() { |
| givenViewAttached(); |
| when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(null); |
| updateSmallestScreenWidth(300); |
| when(mResources.getBoolean( |
| com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); |
| when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); |
| when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(true); |
| |
| updateSmallestScreenWidth(800); |
| |
| verify(mUserSwitcherStubView).inflate(); |
| } |
| |
| @Test |
| public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView() { |
| givenViewAttached(); |
| when(mResources.getBoolean( |
| com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); |
| when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); |
| when(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */)) |
| .thenReturn(false); |
| |
| mNotificationPanelViewController.onFinishInflate(); |
| |
| verify(mUserSwitcherStubView, never()).inflate(); |
| } |
| |
| @Test |
| public void testReInflateViews_userSwitcherDisabled_doNotInflateUserSwitchView() { |
| givenViewAttached(); |
| when(mResources.getBoolean( |
| com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); |
| when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); |
| when(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */)) |
| .thenReturn(false); |
| |
| mNotificationPanelViewController.reInflateViews(); |
| |
| verify(mUserSwitcherStubView, never()).inflate(); |
| } |
| |
| @Test |
| public void testCanCollapsePanelOnTouch_trueForKeyGuard() { |
| mStatusBarStateController.setState(KEYGUARD); |
| |
| assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue(); |
| } |
| |
| @Test |
| public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() { |
| mStatusBarStateController.setState(SHADE); |
| when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true); |
| |
| assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue(); |
| } |
| |
| @Test |
| public void testCanCollapsePanelOnTouch_trueWhenInSettings() { |
| mStatusBarStateController.setState(SHADE); |
| mNotificationPanelViewController.setQsExpanded(true); |
| |
| assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue(); |
| } |
| |
| @Test |
| public void testCanCollapsePanelOnTouch_falseInDualPaneShade() { |
| mStatusBarStateController.setState(SHADE); |
| enableSplitShade(/* enabled= */ true); |
| mNotificationPanelViewController.setQsExpanded(true); |
| |
| assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse(); |
| } |
| |
| @Test |
| public void testSwipeWhileLocked_notifiesKeyguardState() { |
| mStatusBarStateController.setState(KEYGUARD); |
| |
| // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that |
| // the fling occurred and did not dismiss the keyguard. |
| mNotificationPanelViewController.flingToHeight( |
| 0f, true /* expand */, 1000f, 1f, false); |
| verify(mKeyguardStateController).notifyPanelFlingStart(false /* dismissKeyguard */); |
| |
| // Fling un-expanded, which is a keyguard exit fling when we're in KEYGUARD state. |
| mNotificationPanelViewController.flingToHeight( |
| 0f, false /* expand */, 1000f, 1f, false); |
| verify(mKeyguardStateController).notifyPanelFlingStart(true /* dismissKeyguard */); |
| } |
| |
| @Test |
| public void testCancelSwipeWhileLocked_notifiesKeyguardState() { |
| mStatusBarStateController.setState(KEYGUARD); |
| |
| // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that |
| // the fling occurred and did not dismiss the keyguard. |
| mNotificationPanelViewController.flingToHeight( |
| 0f, true /* expand */, 1000f, 1f, false); |
| mNotificationPanelViewController.cancelHeightAnimator(); |
| verify(mKeyguardStateController).notifyPanelFlingEnd(); |
| } |
| |
| @Test |
| public void testSwipe_exactlyToTarget_notifiesNssl() { |
| // No over-expansion |
| mNotificationPanelViewController.setOverExpansion(0f); |
| // Fling to a target that is equal to the current position (i.e. a no-op fling). |
| mNotificationPanelViewController.flingToHeight( |
| 0f, |
| true, |
| mNotificationPanelViewController.getExpandedHeight(), |
| 1f, |
| false); |
| // Verify that the NSSL is notified that the panel is *not* flinging. |
| verify(mNotificationStackScrollLayoutController).setPanelFlinging(false); |
| } |
| |
| @Test |
| public void testDoubleTapRequired_Keyguard() { |
| FalsingManager.FalsingTapListener listener = getFalsingTapListener(); |
| mStatusBarStateController.setState(KEYGUARD); |
| |
| listener.onAdditionalTapRequired(); |
| |
| verify(mKeyguardIndicationController).showTransientIndication(anyInt()); |
| } |
| |
| @Test |
| public void testDoubleTapRequired_ShadeLocked() { |
| FalsingManager.FalsingTapListener listener = getFalsingTapListener(); |
| mStatusBarStateController.setState(SHADE_LOCKED); |
| |
| listener.onAdditionalTapRequired(); |
| |
| verify(mTapAgainViewController).show(); |
| } |
| |
| @Test |
| public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() { |
| mStatusBarStateController.setState(KEYGUARD); |
| mNotificationPanelViewController.setQsExpanded(true); |
| |
| enableSplitShade(true); |
| |
| assertThat(mStatusBarStateController.getState()).isEqualTo(SHADE_LOCKED); |
| } |
| |
| @Test |
| public void testSwitchesToCorrectClockInSinglePaneShade() { |
| mStatusBarStateController.setState(KEYGUARD); |
| |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); |
| triggerPositionClockAndNotifications(); |
| verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); |
| |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); |
| mNotificationPanelViewController.closeQs(); |
| verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true); |
| } |
| |
| @Test |
| public void testSwitchesToCorrectClockInSplitShade() { |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| clearInvocations(mKeyguardStatusViewController); |
| |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); |
| triggerPositionClockAndNotifications(); |
| verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); |
| |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); |
| triggerPositionClockAndNotifications(); |
| verify(mKeyguardStatusViewController, times(2)) |
| .displayClock(LARGE, /* animate */ true); |
| verify(mKeyguardStatusViewController, never()) |
| .displayClock(SMALL, /* animate */ true); |
| } |
| |
| @Test |
| public void testHasNotifications_switchesToLargeClockWhenEnteringSplitShade() { |
| mStatusBarStateController.setState(KEYGUARD); |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); |
| |
| enableSplitShade(/* enabled= */ true); |
| |
| verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); |
| } |
| |
| @Test |
| public void testNoNotifications_switchesToLargeClockWhenEnteringSplitShade() { |
| mStatusBarStateController.setState(KEYGUARD); |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); |
| |
| enableSplitShade(/* enabled= */ true); |
| |
| verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); |
| } |
| |
| @Test |
| public void testHasNotifications_switchesToSmallClockWhenExitingSplitShade() { |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| clearInvocations(mKeyguardStatusViewController); |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); |
| |
| enableSplitShade(/* enabled= */ false); |
| |
| verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true); |
| } |
| |
| @Test |
| public void testNoNotifications_switchesToLargeClockWhenExitingSplitShade() { |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| clearInvocations(mKeyguardStatusViewController); |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); |
| |
| enableSplitShade(/* enabled= */ false); |
| |
| verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); |
| } |
| |
| @Test |
| public void clockSize_mediaShowing_inSplitShade_onAod_isLarge() { |
| when(mDozeParameters.getAlwaysOn()).thenReturn(true); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true); |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| clearInvocations(mKeyguardStatusViewController); |
| |
| mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false); |
| |
| verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate= */ true); |
| } |
| |
| @Test |
| public void clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall() { |
| when(mDozeParameters.getAlwaysOn()).thenReturn(false); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true); |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| clearInvocations(mKeyguardStatusViewController); |
| |
| mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false); |
| |
| verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate= */ true); |
| } |
| |
| @Test |
| public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() { |
| when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| clearInvocations(mKeyguardStatusViewController); |
| when(mMediaDataManager.hasActiveMedia()).thenReturn(true); |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); |
| |
| mNotificationPanelViewController.setDozing(true, false); |
| |
| verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false); |
| } |
| |
| @Test |
| public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() { |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(/* enabled= */ true); |
| clearInvocations(mKeyguardStatusViewController); |
| when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true); |
| |
| // one notification + media player visible |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); |
| triggerPositionClockAndNotifications(); |
| verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true); |
| |
| // only media player visible |
| when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); |
| triggerPositionClockAndNotifications(); |
| verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true); |
| verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true); |
| } |
| |
| @Test |
| public void testLargeScreenHeaderMadeActiveForLargeScreen() { |
| mStatusBarStateController.setState(SHADE); |
| when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true); |
| mNotificationPanelViewController.updateResources(); |
| verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true); |
| |
| when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false); |
| mNotificationPanelViewController.updateResources(); |
| verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false); |
| } |
| |
| @Test |
| public void testExpandWithQsMethodIsUsingLockscreenTransitionController() { |
| enableSplitShade(/* enabled= */ true); |
| mStatusBarStateController.setState(KEYGUARD); |
| |
| mNotificationPanelViewController.expandWithQs(); |
| |
| verify(mLockscreenShadeTransitionController).goToLockedShade( |
| /* expandedView= */null, /* needsQSAnimation= */false); |
| } |
| |
| @Test |
| public void testUnlockAnimationDoesNotAffectScrim() { |
| mNotificationPanelViewController.onUnlockHintStarted(); |
| verify(mScrimController).setExpansionAffectsAlpha(false); |
| mNotificationPanelViewController.onUnlockHintFinished(); |
| verify(mScrimController).setExpansionAffectsAlpha(true); |
| } |
| |
| @Test |
| public void testUnlockHintAnimation_runs_whenNotInPowerSaveMode_andDozeAmountIsZero() { |
| when(mPowerManager.isPowerSaveMode()).thenReturn(false); |
| when(mAmbientState.getDozeAmount()).thenReturn(0f); |
| mNotificationPanelViewController.startUnlockHintAnimation(); |
| assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isTrue(); |
| } |
| |
| @Test |
| public void testUnlockHintAnimation_doesNotRun_inPowerSaveMode() { |
| when(mPowerManager.isPowerSaveMode()).thenReturn(true); |
| mNotificationPanelViewController.startUnlockHintAnimation(); |
| assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse(); |
| } |
| |
| @Test |
| public void testUnlockHintAnimation_doesNotRun_whenDozeAmountNotZero() { |
| when(mPowerManager.isPowerSaveMode()).thenReturn(false); |
| when(mAmbientState.getDozeAmount()).thenReturn(0.5f); |
| mNotificationPanelViewController.startUnlockHintAnimation(); |
| assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse(); |
| } |
| |
| @Test |
| public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() { |
| float statusBarAlpha = 0.5f; |
| |
| mNotificationPanelViewController.setKeyguardStatusBarAlpha(statusBarAlpha); |
| |
| verify(mKeyguardStatusBarViewController).setAlpha(statusBarAlpha); |
| } |
| |
| @Test |
| public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() { |
| enableSplitShade(/* enabled= */ true); |
| mShadeExpansionStateManager.updateState(STATE_CLOSED); |
| assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); |
| |
| mShadeExpansionStateManager.updateState(STATE_OPENING); |
| |
| assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue(); |
| } |
| |
| @Test |
| public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() { |
| enableSplitShade(/* enabled= */ true); |
| mShadeExpansionStateManager.updateState(STATE_CLOSED); |
| |
| mStatusBarStateController.setState(KEYGUARD); |
| // going to lockscreen would trigger STATE_OPENING |
| mShadeExpansionStateManager.updateState(STATE_OPENING); |
| |
| assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); |
| } |
| |
| @Test |
| public void testQsImmediateResetsWhenPanelOpensOrCloses() { |
| mNotificationPanelViewController.mQsExpandImmediate = true; |
| mShadeExpansionStateManager.updateState(STATE_OPEN); |
| assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); |
| |
| mNotificationPanelViewController.mQsExpandImmediate = true; |
| mShadeExpansionStateManager.updateState(STATE_CLOSED); |
| assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); |
| } |
| |
| @Test |
| public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() { |
| // to make sure shade is in expanded state |
| mNotificationPanelViewController.startWaitingForOpenPanelGesture(); |
| assertThat(mNotificationPanelViewController.isQsExpanded()).isFalse(); |
| |
| // switch to split shade from portrait (default state) |
| enableSplitShade(/* enabled= */ true); |
| assertThat(mNotificationPanelViewController.isQsExpanded()).isTrue(); |
| |
| // switch to portrait from split shade |
| enableSplitShade(/* enabled= */ false); |
| assertThat(mNotificationPanelViewController.isQsExpanded()).isFalse(); |
| } |
| |
| @Test |
| public void testPanelClosedWhenClosingQsInSplitShade() { |
| mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, |
| /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0); |
| enableSplitShade(/* enabled= */ true); |
| mNotificationPanelViewController.setExpandedFraction(1f); |
| |
| assertThat(mNotificationPanelViewController.isClosing()).isFalse(); |
| mNotificationPanelViewController.animateCloseQs(false); |
| assertThat(mNotificationPanelViewController.isClosing()).isTrue(); |
| } |
| |
| @Test |
| public void testPanelStaysOpenWhenClosingQs() { |
| mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, |
| /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0); |
| mNotificationPanelViewController.setExpandedFraction(1f); |
| |
| assertThat(mNotificationPanelViewController.isClosing()).isFalse(); |
| mNotificationPanelViewController.animateCloseQs(false); |
| assertThat(mNotificationPanelViewController.isClosing()).isFalse(); |
| } |
| |
| @Test |
| public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { |
| mNotificationPanelViewController.mQs = mQs; |
| when(mQsFrame.getX()).thenReturn(0f); |
| when(mQsFrame.getWidth()).thenReturn(1000); |
| when(mQsHeader.getTop()).thenReturn(0); |
| when(mQsHeader.getBottom()).thenReturn(1000); |
| NotificationPanelViewController.TouchHandler touchHandler = |
| mNotificationPanelViewController.createTouchHandler(); |
| |
| mNotificationPanelViewController.setExpandedFraction(1f); |
| touchHandler.onInterceptTouchEvent( |
| createMotionEvent(/* x= */ 0, /* y= */ 0, MotionEvent.ACTION_DOWN)); |
| touchHandler.onInterceptTouchEvent( |
| createMotionEvent(/* x= */ 0, /* y= */ 500, MotionEvent.ACTION_MOVE)); |
| |
| assertThat(mNotificationPanelViewController.isQsTracking()).isTrue(); |
| } |
| |
| @Test |
| public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() { |
| enableSplitShade(true); |
| mNotificationPanelViewController.mQs = mQs; |
| when(mQsFrame.getX()).thenReturn(0f); |
| when(mQsFrame.getWidth()).thenReturn(1000); |
| when(mQsHeader.getTop()).thenReturn(0); |
| when(mQsHeader.getBottom()).thenReturn(1000); |
| NotificationPanelViewController.TouchHandler touchHandler = |
| mNotificationPanelViewController.createTouchHandler(); |
| |
| mNotificationPanelViewController.setExpandedFraction(1f); |
| touchHandler.onInterceptTouchEvent( |
| createMotionEvent(/* x= */ 0, /* y= */ 0, MotionEvent.ACTION_DOWN)); |
| touchHandler.onInterceptTouchEvent( |
| createMotionEvent(/* x= */ 0, /* y= */ 500, MotionEvent.ACTION_MOVE)); |
| |
| assertThat(mNotificationPanelViewController.isQsTracking()).isFalse(); |
| } |
| |
| @Test |
| public void testOnAttachRefreshStatusBarState() { |
| mStatusBarStateController.setState(KEYGUARD); |
| when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(false); |
| for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { |
| listener.onViewAttachedToWindow(mView); |
| } |
| verify(mKeyguardStatusViewController).setKeyguardStatusViewVisibility( |
| KEYGUARD/*statusBarState*/, |
| false/*keyguardFadingAway*/, |
| false/*goingToFullShade*/, SHADE/*oldStatusBarState*/); |
| } |
| |
| @Test |
| public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() { |
| enableSplitShade(true); |
| mNotificationPanelViewController.expandWithQs(); |
| |
| int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); |
| |
| assertThat(maxDistance).isEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); |
| } |
| |
| @Test |
| public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() { |
| enableSplitShade(true); |
| mNotificationPanelViewController.expandWithQs(); |
| when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true); |
| mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( |
| SPLIT_SHADE_FULL_TRANSITION_DISTANCE); |
| |
| int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); |
| |
| assertThat(maxDistance).isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); |
| } |
| |
| @Test |
| public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() { |
| mStatusBarStateController.setState(KEYGUARD); |
| enableSplitShade(true); |
| mNotificationPanelViewController.expandWithQs(); |
| |
| int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); |
| |
| assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); |
| } |
| |
| @Test |
| public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() { |
| enableSplitShade(false); |
| mNotificationPanelViewController.expandWithQs(); |
| |
| int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); |
| |
| assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); |
| } |
| |
| @Test |
| public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() { |
| mNotificationPanelViewController.mQs = mQs; |
| |
| setIsFullWidth(true); |
| |
| verify(mQs).setIsNotificationPanelFullWidth(true); |
| } |
| |
| @Test |
| public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() { |
| mNotificationPanelViewController.mQs = mQs; |
| |
| setIsFullWidth(false); |
| |
| verify(mQs).setIsNotificationPanelFullWidth(false); |
| } |
| |
| @Test |
| public void onLayoutChange_qsNotSet_doesNotCrash() { |
| mNotificationPanelViewController.mQs = null; |
| |
| triggerLayoutChange(); |
| } |
| |
| @Test |
| public void onQsFragmentAttached_fullWidth_setsFullWidthTrueOnQS() { |
| setIsFullWidth(true); |
| givenViewAttached(); |
| mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); |
| |
| verify(mQSFragment).setIsNotificationPanelFullWidth(true); |
| } |
| |
| @Test |
| public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() { |
| setIsFullWidth(false); |
| givenViewAttached(); |
| mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); |
| |
| verify(mQSFragment).setIsNotificationPanelFullWidth(false); |
| } |
| |
| @Test |
| public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() { |
| float squishinessFraction = 0.456f; |
| mNotificationPanelViewController.mQs = mQs; |
| when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) |
| .thenReturn(squishinessFraction); |
| when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) |
| .thenReturn(0.987f); |
| // Call setTransitionToFullShadeAmount to get into the full shade transition in progress |
| // state. |
| mNotificationPanelViewController.setTransitionToFullShadeAmount( |
| /* pxAmount= */ 234, |
| /* animate= */ false, |
| /* delay= */ 0 |
| ); |
| |
| mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123); |
| |
| // First for setTransitionToFullShadeAmount and then setQsExpansion |
| verify(mQs, times(2)).setQsExpansion( |
| /* expansion= */ anyFloat(), |
| /* panelExpansionFraction= */ anyFloat(), |
| /* proposedTranslation= */ anyFloat(), |
| eq(squishinessFraction) |
| ); |
| } |
| |
| @Test |
| public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() { |
| float lsSquishinessFraction = 0.456f; |
| float nsslSquishinessFraction = 0.987f; |
| mNotificationPanelViewController.mQs = mQs; |
| when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) |
| .thenReturn(lsSquishinessFraction); |
| when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) |
| .thenReturn(nsslSquishinessFraction); |
| |
| mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123); |
| |
| verify(mQs).setQsExpansion( |
| /* expansion= */ anyFloat(), |
| /* panelExpansionFraction= */ anyFloat(), |
| /* proposedTranslation= */ anyFloat(), |
| eq(nsslSquishinessFraction) |
| ); |
| } |
| |
| @Test |
| public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() { |
| StatusBarStateController.StateListener statusBarStateListener = |
| mNotificationPanelViewController.mStatusBarStateListener; |
| statusBarStateListener.onStateChanged(KEYGUARD); |
| mNotificationPanelViewController.setDozing(false, false); |
| |
| // This sets the dozing state that is read when onMiddleClicked is eventually invoked. |
| mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); |
| mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); |
| |
| verify(mUpdateMonitor).requestFaceAuth(true, |
| FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); |
| } |
| |
| @Test |
| public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() { |
| StatusBarStateController.StateListener statusBarStateListener = |
| mNotificationPanelViewController.mStatusBarStateListener; |
| statusBarStateListener.onStateChanged(KEYGUARD); |
| mNotificationPanelViewController.setDozing(false, false); |
| when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false); |
| |
| // This sets the dozing state that is read when onMiddleClicked is eventually invoked. |
| mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); |
| mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); |
| |
| verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true); |
| } |
| |
| @Test |
| public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() { |
| StatusBarStateController.StateListener statusBarStateListener = |
| mNotificationPanelViewController.mStatusBarStateListener; |
| statusBarStateListener.onStateChanged(KEYGUARD); |
| mNotificationPanelViewController.setDozing(false, false); |
| when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true); |
| |
| // This sets the dozing state that is read when onMiddleClicked is eventually invoked. |
| mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); |
| mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); |
| |
| verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true); |
| } |
| |
| @Test |
| public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() { |
| StatusBarStateController.StateListener statusBarStateListener = |
| mNotificationPanelViewController.mStatusBarStateListener; |
| statusBarStateListener.onStateChanged(KEYGUARD); |
| mNotificationPanelViewController.setDozing(true, false); |
| |
| // This sets the dozing state that is read when onMiddleClicked is eventually invoked. |
| mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); |
| mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); |
| |
| verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString()); |
| } |
| |
| @Test |
| public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() { |
| StatusBarStateController.StateListener statusBarStateListener = |
| mNotificationPanelViewController.mStatusBarStateListener; |
| statusBarStateListener.onStateChanged(SHADE_LOCKED); |
| |
| mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); |
| |
| verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString()); |
| |
| } |
| |
| /** |
| * When shade is flinging to close and this fling is not intercepted, |
| * {@link AmbientState#setIsClosing(boolean)} should be called before |
| * {@link NotificationStackScrollLayoutController#onExpansionStopped()} |
| * to ensure scrollY can be correctly set to be 0 |
| */ |
| @Test |
| public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() { |
| // Given: Shade is expanded |
| mNotificationPanelViewController.notifyExpandingFinished(); |
| mNotificationPanelViewController.setIsClosing(false); |
| |
| // When: Shade flings to close not canceled |
| mNotificationPanelViewController.notifyExpandingStarted(); |
| mNotificationPanelViewController.setIsClosing(true); |
| mNotificationPanelViewController.onFlingEnd(false); |
| |
| // Then: AmbientState's mIsClosing should be set to false |
| // before mNotificationStackScrollLayoutController.onExpansionStopped() is called |
| // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition |
| // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed |
| InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController); |
| inOrder.verify(mAmbientState).setIsClosing(false); |
| inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped(); |
| } |
| |
| private static MotionEvent createMotionEvent(int x, int y, int action) { |
| return MotionEvent.obtain( |
| /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0); |
| } |
| |
| private void triggerPositionClockAndNotifications() { |
| mNotificationPanelViewController.closeQs(); |
| } |
| |
| private FalsingManager.FalsingTapListener getFalsingTapListener() { |
| for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { |
| listener.onViewAttachedToWindow(mView); |
| } |
| assertThat(mFalsingManager.getTapListeners().size()).isEqualTo(1); |
| return mFalsingManager.getTapListeners().get(0); |
| } |
| |
| private void givenViewAttached() { |
| for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { |
| listener.onViewAttachedToWindow(mView); |
| } |
| } |
| |
| private ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) { |
| ConstraintSet constraintSet = new ConstraintSet(); |
| constraintSet.clone(mNotificationContainerParent); |
| return constraintSet.getConstraint(id).layout; |
| } |
| |
| private void enableSplitShade(boolean enabled) { |
| when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled); |
| mNotificationPanelViewController.updateResources(); |
| } |
| |
| private void updateMultiUserSetting(boolean enabled) { |
| when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); |
| when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(enabled); |
| final ArgumentCaptor<ContentObserver> observerCaptor = |
| ArgumentCaptor.forClass(ContentObserver.class); |
| verify(mContentResolver) |
| .registerContentObserver(any(), anyBoolean(), observerCaptor.capture()); |
| observerCaptor.getValue().onChange(/* selfChange */ false); |
| } |
| |
| private void updateSmallestScreenWidth(int smallestScreenWidthDp) { |
| Configuration configuration = new Configuration(); |
| configuration.smallestScreenWidthDp = smallestScreenWidthDp; |
| mConfigurationController.onConfigurationChanged(configuration); |
| } |
| |
| private void onTouchEvent(MotionEvent ev) { |
| mTouchHandler.onTouch(mView, ev); |
| } |
| |
| private void setDozing(boolean dozing, boolean dozingAlwaysOn) { |
| when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn); |
| mNotificationPanelViewController.setDozing( |
| /* dozing= */ dozing, |
| /* animate= */ false |
| ); |
| } |
| |
| private void assertKeyguardStatusViewCentered() { |
| mNotificationPanelViewController.updateResources(); |
| assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isAnyOf( |
| ConstraintSet.PARENT_ID, ConstraintSet.UNSET); |
| } |
| |
| private void assertKeyguardStatusViewNotCentered() { |
| mNotificationPanelViewController.updateResources(); |
| assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isEqualTo( |
| R.id.qs_edge_guideline); |
| } |
| |
| private void setIsFullWidth(boolean fullWidth) { |
| float nsslWidth = fullWidth ? PANEL_WIDTH : PANEL_WIDTH / 2f; |
| when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(nsslWidth); |
| triggerLayoutChange(); |
| } |
| |
| private void triggerLayoutChange() { |
| mLayoutChangeListener.onLayoutChange( |
| mView, |
| /* left= */ 0, |
| /* top= */ 0, |
| /* right= */ 0, |
| /* bottom= */ 0, |
| /* oldLeft= */ 0, |
| /* oldTop= */ 0, |
| /* oldRight= */ 0, |
| /* oldBottom= */ 0 |
| ); |
| } |
| } |