diff options
13 files changed, 515 insertions, 123 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 0424382a1b88..a03d84956ddc 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -16,7 +16,6 @@ package com.android.systemui.plugins.qs; import android.view.View; import android.view.View.OnClickListener; -import android.view.ViewGroup; import com.android.systemui.plugins.FragmentBase; import com.android.systemui.plugins.annotations.DependsOn; @@ -56,7 +55,7 @@ public interface QS extends FragmentBase { void setQsExpansion(float qsExpansionFraction, float headerTranslation); void setHeaderListening(boolean listening); void notifyCustomizeChanged(); - void setContainer(ViewGroup container); + void setContainerController(QSContainerController controller); void setExpandClickListener(OnClickListener onClickListener); View getHeader(); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt new file mode 100644 index 000000000000..8bf982d360a1 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt @@ -0,0 +1,9 @@ +package com.android.systemui.plugins.qs + +interface QSContainerController { + fun setCustomizerAnimating(animating: Boolean) + + fun setCustomizerShowing(showing: Boolean) + + fun setDetailShowing(showing: Boolean) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 8e436611e805..3fc4f504d007 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -26,7 +26,6 @@ import android.graphics.Point; import android.graphics.PointF; import android.util.AttributeSet; import android.view.View; -import android.view.WindowInsets; import android.widget.FrameLayout; import android.widget.ImageView; @@ -58,7 +57,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private int mSideMargins; private boolean mQsDisabled; private int mContentPadding = -1; - private int mNavBarInset = 0; private boolean mClippingEnabled; public QSContainerImpl(Context context, AttributeSet attrs) { @@ -95,24 +93,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mNavBarInset = insets.getInsets(WindowInsets.Type.navigationBars()).bottom; - mQSPanelContainer.setPaddingRelative( - mQSPanelContainer.getPaddingStart(), - mQSPanelContainer.getPaddingTop(), - mQSPanelContainer.getPaddingEnd(), - mNavBarInset - ); - return super.onApplyWindowInsets(insets); - } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the // bottom and footer are inside the screen. MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); - int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin + int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec); + int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin - getPaddingBottom(); int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin + layoutParams.rightMargin; @@ -122,11 +109,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); int width = mQSPanelContainer.getMeasuredWidth() + padding; super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY)); // QSCustomizer will always be the height of the screen, but do this after // other measuring to avoid changing the height of the QS. mQSCustomizer.measure(widthMeasureSpec, - MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 929927e5d4e4..58a942a05cbc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -46,8 +46,8 @@ import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; public class QSDetail extends LinearLayout { @@ -86,7 +86,7 @@ public class QSDetail extends LinearLayout { private boolean mSwitchState; private QSFooter mFooter; - private NotificationsQuickSettingsContainer mContainer; + private QSContainerController mQsContainerController; public QSDetail(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -120,8 +120,8 @@ public class QSDetail extends LinearLayout { mClipper = new QSDetailClipper(this); } - public void setContainer(NotificationsQuickSettingsContainer container) { - mContainer = container; + public void setContainerController(QSContainerController controller) { + mQsContainerController = controller; } /** */ @@ -262,8 +262,8 @@ public class QSDetail extends LinearLayout { } sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); animateDetailVisibleDiff(x, y, visibleDiff, listener); - if (mContainer != null) { - mContainer.setDetailShowing(showingDetail); + if (mQsContainerController != null) { + mQsContainerController.setDetailShowing(showingDetail); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 6fd81411dbc0..b8376da8a885 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -43,6 +43,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; @@ -50,7 +51,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.util.InjectionInflationController; @@ -336,11 +336,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public void setContainer(ViewGroup container) { - if (container instanceof NotificationsQuickSettingsContainer) { - mQSCustomizerController.setContainer((NotificationsQuickSettingsContainer) container); - mQSDetail.setContainer((NotificationsQuickSettingsContainer) container); - } + public void setContainerController(QSContainerController controller) { + mQSCustomizerController.setContainerController(controller); + mQSDetail.setContainerController(controller); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index d33982c6e172..1a6d49070a2f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -33,9 +33,9 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.systemui.R; import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.qs.QSDetailClipper; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; /** * Allows full-screen customization of QS, through show() and hide(). @@ -54,7 +54,7 @@ public class QSCustomizer extends LinearLayout { private boolean isShown; private final RecyclerView mRecyclerView; private boolean mCustomizing; - private NotificationsQuickSettingsContainer mNotifQsContainer; + private QSContainerController mQsContainerController; private QS mQs; private int mX; private int mY; @@ -103,8 +103,8 @@ public class QSCustomizer extends LinearLayout { lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown); } - public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) { - mNotifQsContainer = notificationsQsContainer; + public void setContainerController(QSContainerController controller) { + mQsContainerController = controller; } public void setQs(QS qs) { @@ -123,8 +123,8 @@ public class QSCustomizer extends LinearLayout { mOpening = true; setVisibility(View.VISIBLE); mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter)); - mNotifQsContainer.setCustomizerAnimating(true); - mNotifQsContainer.setCustomizerShowing(true); + mQsContainerController.setCustomizerAnimating(true); + mQsContainerController.setCustomizerShowing(true); } } @@ -136,8 +136,8 @@ public class QSCustomizer extends LinearLayout { mClipper.showBackground(); isShown = true; setCustomizing(true); - mNotifQsContainer.setCustomizerAnimating(false); - mNotifQsContainer.setCustomizerShowing(true); + mQsContainerController.setCustomizerAnimating(false); + mQsContainerController.setCustomizerShowing(true); } } @@ -154,8 +154,8 @@ public class QSCustomizer extends LinearLayout { } else { setVisibility(View.GONE); } - mNotifQsContainer.setCustomizerAnimating(animate); - mNotifQsContainer.setCustomizerShowing(false); + mQsContainerController.setCustomizerAnimating(animate); + mQsContainerController.setCustomizerShowing(false); } } @@ -193,7 +193,7 @@ public class QSCustomizer extends LinearLayout { setCustomizing(true); } mOpening = false; - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); mRecyclerView.setAdapter(mTileAdapter); } @@ -201,7 +201,7 @@ public class QSCustomizer extends LinearLayout { public void onAnimationCancel(Animator animation) { mOpening = false; mQs.notifyCustomizeChanged(); - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); } } @@ -211,7 +211,7 @@ public class QSCustomizer extends LinearLayout { if (!isShown) { setVisibility(View.GONE); } - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); } @Override @@ -219,7 +219,7 @@ public class QSCustomizer extends LinearLayout { if (!isShown) { setVisibility(View.GONE); } - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index 49d18e62346a..618a429f6b51 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -35,13 +35,13 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -233,8 +233,8 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { } /** */ - public void setContainer(NotificationsQuickSettingsContainer container) { - mView.setContainer(container); + public void setContainerController(QSContainerController controller) { + mView.setContainerController(controller); } public boolean isShown() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 4862d1617837..ab0499bc0dde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -310,6 +310,7 @@ public class NotificationPanelViewController extends PanelViewController { private KeyguardStatusViewController mKeyguardStatusViewController; private LockIconViewController mLockIconViewController; private NotificationsQuickSettingsContainer mNotificationContainerParent; + private NotificationsQSContainerController mNotificationsQSContainerController; private boolean mAnimateNextPositionUpdate; private float mQuickQsOffsetHeight; private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @@ -653,6 +654,7 @@ public class NotificationPanelViewController extends PanelViewController { ConversationNotificationManager conversationNotificationManager, MediaHierarchyManager mediaHierarchyManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + NotificationsQSContainerController notificationsQSContainerController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory, @@ -708,6 +710,8 @@ public class NotificationPanelViewController extends PanelViewController { mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; mMediaHierarchyManager = mediaHierarchyManager; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mNotificationsQSContainerController = notificationsQSContainerController; + mNotificationsQSContainerController.init(); mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mGroupManager = groupManager; mNotificationIconAreaController = notificationIconAreaController; @@ -1005,7 +1009,7 @@ public class NotificationPanelViewController extends PanelViewController { constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin); constraintSet.setMargin(R.id.qs_frame, TOP, topMargin); constraintSet.applyTo(mNotificationContainerParent); - mNotificationContainerParent.setSplitShadeEnabled(mShouldUseSplitNotificationShade); + mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade); updateKeyguardStatusViewAlignment(/* animate= */false); @@ -2106,7 +2110,7 @@ public class NotificationPanelViewController extends PanelViewController { requestPanelHeightUpdate(); mFalsingCollector.setQsExpanded(expanded); mStatusBar.setQsExpanded(expanded); - mNotificationContainerParent.setQsExpanded(expanded); + mNotificationsQSContainerController.setQsExpanded(expanded); mPulseExpansionHandler.setQsExpanded(expanded); mKeyguardBypassController.setQSExpanded(expanded); mStatusBarKeyguardViewManager.setQsExpanded(expanded); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt new file mode 100644 index 000000000000..34bb6d3e1a27 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt @@ -0,0 +1,141 @@ +package com.android.systemui.statusbar.phone + +import android.view.WindowInsets +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.plugins.qs.QS +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.shared.system.QuickStepContract +import com.android.systemui.util.ViewController +import java.util.function.Consumer +import javax.inject.Inject + +class NotificationsQSContainerController @Inject constructor( + view: NotificationsQuickSettingsContainer, + private val navigationModeController: NavigationModeController, + private val overviewProxyService: OverviewProxyService +) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { + + var qsExpanded = false + set(value) { + if (field != value) { + field = value + mView.invalidate() + } + } + var splitShadeEnabled = false + set(value) { + if (field != value) { + field = value + // in case device configuration changed while showing QS details/customizer + updateBottomSpacing() + } + } + + private var isQSDetailShowing = false + private var isQSCustomizing = false + private var isQSCustomizerAnimating = false + + private var notificationsBottomMargin = 0 + private var bottomStableInsets = 0 + private var bottomCutoutInsets = 0 + + private var isGestureNavigation = true + private var taskbarVisible = false + private val taskbarVisibilityListener: OverviewProxyListener = object : OverviewProxyListener { + override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) { + taskbarVisible = visible + } + } + private val windowInsetsListener: Consumer<WindowInsets> = Consumer { insets -> + // when taskbar is visible, stableInsetBottom will include its height + bottomStableInsets = insets.stableInsetBottom + bottomCutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0 + updateBottomSpacing() + } + + override fun onInit() { + val currentMode: Int = navigationModeController.addListener { mode: Int -> + isGestureNavigation = QuickStepContract.isGesturalMode(mode) + } + isGestureNavigation = QuickStepContract.isGesturalMode(currentMode) + } + + public override fun onViewAttached() { + notificationsBottomMargin = mView.defaultNotificationsMarginBottom + overviewProxyService.addCallback(taskbarVisibilityListener) + mView.setInsetsChangedListener(windowInsetsListener) + mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } + } + + override fun onViewDetached() { + overviewProxyService.removeCallback(taskbarVisibilityListener) + mView.removeOnInsetsChangedListener() + mView.removeQSFragmentAttachedListener() + } + + override fun setCustomizerAnimating(animating: Boolean) { + if (isQSCustomizerAnimating != animating) { + isQSCustomizerAnimating = animating + mView.invalidate() + } + } + + override fun setCustomizerShowing(showing: Boolean) { + isQSCustomizing = showing + updateBottomSpacing() + } + + override fun setDetailShowing(showing: Boolean) { + isQSDetailShowing = showing + updateBottomSpacing() + } + + private fun updateBottomSpacing() { + val (containerPadding, notificationsMargin) = calculateBottomSpacing() + var qsScrollPaddingBottom = 0 + if (!(splitShadeEnabled || isQSCustomizing || isQSDetailShowing || isGestureNavigation || + taskbarVisible)) { + // no taskbar, portrait, navigation buttons enabled: + // padding is needed so QS can scroll up over bottom insets - to reach the point when + // the whole QS is above bottom insets + qsScrollPaddingBottom = bottomStableInsets + } + mView.setPadding(0, 0, 0, containerPadding) + mView.setNotificationsMarginBottom(notificationsMargin) + mView.setQSScrollPaddingBottom(qsScrollPaddingBottom) + } + + private fun calculateBottomSpacing(): Pair<Int, Int> { + val containerPadding: Int + var stackScrollMargin = notificationsBottomMargin + if (splitShadeEnabled) { + if (isGestureNavigation) { + // only default cutout padding, taskbar always hides + containerPadding = bottomCutoutInsets + } else if (taskbarVisible) { + // navigation buttons + visible taskbar means we're NOT on homescreen + containerPadding = bottomStableInsets + } else { + // navigation buttons + hidden taskbar means we're on homescreen + containerPadding = 0 + // we need extra margin for notifications as navigation buttons are below them + stackScrollMargin = bottomStableInsets + notificationsBottomMargin + } + } else { + if (isQSCustomizing || isQSDetailShowing) { + // Clear out bottom paddings/margins so the qs customization can be full height. + containerPadding = 0 + stackScrollMargin = 0 + } else if (isGestureNavigation) { + containerPadding = bottomCutoutInsets + } else if (taskbarVisible) { + containerPadding = bottomStableInsets + } else { + containerPadding = 0 + } + } + return containerPadding to stackScrollMargin + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index 68e28cdba975..9210a8b5db80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver; import java.util.ArrayList; import java.util.Comparator; +import java.util.function.Consumer; /** * The container with notification stack scroller and quick settings inside. @@ -43,17 +44,15 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private View mQsFrame; private View mStackScroller; private View mKeyguardStatusBar; - private boolean mQsExpanded; - private boolean mCustomizerAnimating; - private boolean mCustomizing; - private boolean mDetailShowing; - private int mBottomPadding; private int mStackScrollerMargin; private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>(); private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>(); private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild); - private boolean mSplitShadeEnabled; + private Consumer<WindowInsets> mInsetsChangedListener = insets -> {}; + private Consumer<QS> mQSFragmentAttachedListener = qs -> {}; + private QS mQs; + private View mQSScrollView; public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -69,6 +68,59 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } @Override + public void onFragmentViewCreated(String tag, Fragment fragment) { + mQs = (QS) fragment; + mQSFragmentAttachedListener.accept(mQs); + mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view); + } + + @Override + public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) { + invalidate(); + } + + public void setNotificationsMarginBottom(int margin) { + LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams(); + params.bottomMargin = margin; + mStackScroller.setLayoutParams(params); + } + + public void setQSScrollPaddingBottom(int paddingBottom) { + if (mQSScrollView != null) { + mQSScrollView.setPaddingRelative( + mQSScrollView.getPaddingLeft(), + mQSScrollView.getPaddingTop(), + mQSScrollView.getPaddingRight(), + paddingBottom + ); + } + } + + public int getDefaultNotificationsMarginBottom() { + return mStackScrollerMargin; + } + + public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) { + mInsetsChangedListener = onInsetsChangedListener; + } + + public void removeOnInsetsChangedListener() { + mInsetsChangedListener = insets -> {}; + } + + public void setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener) { + mQSFragmentAttachedListener = qsFragmentAttachedListener; + // listener might be attached after fragment is attached + if (mQs != null) { + mQSFragmentAttachedListener.accept(mQs); + } + } + + public void removeQSFragmentAttachedListener() { + mQSFragmentAttachedListener = qs -> {}; + } + + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); FragmentHostManager.get(this).addTagListener(QS.TAG, this); @@ -82,8 +134,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mBottomPadding = insets.getStableInsetBottom(); - setPadding(0, 0, 0, mBottomPadding); + mInsetsChangedListener.accept(insets); return insets; } @@ -119,66 +170,4 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } } - @Override - public void onFragmentViewCreated(String tag, Fragment fragment) { - QS container = (QS) fragment; - container.setContainer(this); - } - - public void setQsExpanded(boolean expanded) { - if (mQsExpanded != expanded) { - mQsExpanded = expanded; - invalidate(); - } - } - - public void setCustomizerAnimating(boolean isAnimating) { - if (mCustomizerAnimating != isAnimating) { - mCustomizerAnimating = isAnimating; - invalidate(); - } - } - - public void setCustomizerShowing(boolean isShowing) { - mCustomizing = isShowing; - updateBottomMargin(); - } - - public void setDetailShowing(boolean isShowing) { - mDetailShowing = isShowing; - updateBottomMargin(); - } - - /** - * Sets if split shade is enabled and adjusts margins/paddings depending on QS details and - * customizer state - */ - public void setSplitShadeEnabled(boolean splitShadeEnabled) { - mSplitShadeEnabled = splitShadeEnabled; - // in case device was rotated while showing QS details/customizer - updateBottomMargin(); - } - - private void updateBottomMargin() { - // in split shade, QS state changes should not influence notifications panel - if (!mSplitShadeEnabled && (mCustomizing || mDetailShowing)) { - // Clear out bottom paddings/margins so the qs customization can be full height. - setPadding(0, 0, 0, 0); - setBottomMargin(mStackScroller, 0); - } else { - setPadding(0, 0, 0, mBottomPadding); - setBottomMargin(mStackScroller, mStackScrollerMargin); - } - } - - private void setBottomMargin(View v, int bottomMargin) { - LayoutParams params = (LayoutParams) v.getLayoutParams(); - params.bottomMargin = bottomMargin; - v.setLayoutParams(params); - } - - @Override - public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) { - invalidate(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index ecf3b86561b9..2791678c3b4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -29,9 +29,9 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.NotificationPanelView; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; +import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.phone.TapAgainView; import com.android.systemui.util.InjectionInflationController; @@ -67,8 +67,8 @@ public abstract class StatusBarViewModule { @Provides @StatusBarComponent.StatusBarScope public static NotificationStackScrollLayout providesNotificationStackScrollLayout( - NotificationStackScrollLayoutController notificationStackScrollLayoutController) { - return notificationStackScrollLayoutController.getView(); + NotificationShadeWindowView notificationShadeWindowView) { + return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller); } /** */ @@ -149,4 +149,12 @@ public abstract class StatusBarViewModule { public static TapAgainView getTapAgainView(NotificationPanelView npv) { return npv.getTapAgainView(); } + + /** */ + @Provides + @StatusBarComponent.StatusBarScope + public static NotificationsQuickSettingsContainer getNotificationsQuickSettingsContainer( + NotificationShadeWindowView notificationShadeWindowView) { + return notificationShadeWindowView.findViewById(R.id.notification_container_parent); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index f7423bb7951d..253077f4ab08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -300,6 +300,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private LockscreenGestureLogger mLockscreenGestureLogger; @Mock private DumpManager mDumpManager; + @Mock + private NotificationsQSContainerController mNotificationsQSContainerController; private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; @@ -414,6 +416,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, mConversationNotificationManager, mMediaHiearchyManager, mStatusBarKeyguardViewManager, + mNotificationsQSContainerController, mNotificationStackScrollLayoutController, mKeyguardStatusViewComponentFactory, mKeyguardQsUserSwitchComponentFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt new file mode 100644 index 000000000000..337e64592750 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt @@ -0,0 +1,254 @@ +package com.android.systemui.statusbar.phone + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.WindowInsets +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener +import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.RETURNS_DEEP_STUBS +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.function.Consumer +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class NotificationQSContainerControllerTest : SysuiTestCase() { + + companion object { + const val STABLE_INSET_BOTTOM = 100 + const val CUTOUT_HEIGHT = 50 + const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL + const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + const val NOTIFICATIONS_MARGIN = 50 + } + + @Mock + private lateinit var navigationModeController: NavigationModeController + @Mock + private lateinit var overviewProxyService: OverviewProxyService + @Mock + private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer + @Captor + lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> + @Captor + lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener> + @Captor + lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>> + + private lateinit var notificationsQSContainerController: NotificationsQSContainerController + private lateinit var navigationModeCallback: ModeChangedListener + private lateinit var taskbarVisibilityCallback: OverviewProxyListener + private lateinit var windowInsetsCallback: Consumer<WindowInsets> + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + notificationsQSContainerController = NotificationsQSContainerController( + notificationsQSContainer, + navigationModeController, + overviewProxyService + ) + whenever(notificationsQSContainer.defaultNotificationsMarginBottom) + .thenReturn(NOTIFICATIONS_MARGIN) + whenever(navigationModeController.addListener(navigationModeCaptor.capture())) + .thenReturn(GESTURES_NAVIGATION) + doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture()) + doNothing().`when`(notificationsQSContainer) + .setInsetsChangedListener(windowInsetsCallbackCaptor.capture()) + + notificationsQSContainerController.init() + notificationsQSContainerController.onViewAttached() + + navigationModeCallback = navigationModeCaptor.value + taskbarVisibilityCallback = taskbarVisibilityCaptor.value + windowInsetsCallback = windowInsetsCallbackCaptor.value + } + + @Test + fun testTaskbarVisibleInSplitShade() { + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = true, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded + expectedNotificationsMargin = NOTIFICATIONS_MARGIN) + + given(taskbarVisible = true, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = STABLE_INSET_BOTTOM, + expectedNotificationsMargin = NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarNotVisibleInSplitShade() { + // when taskbar is not visible, it means we're on the home screen + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons + expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarNotVisibleInSplitShadeWithCutout() { + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withCutout()) + then(expectedContainerPadding = CUTOUT_HEIGHT) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withCutout().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarVisibleInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + given(taskbarVisible = true, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = true, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = STABLE_INSET_BOTTOM) + } + + @Test + fun testTaskbarNotVisibleInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withCutout().withStableBottom()) + then(expectedContainerPadding = CUTOUT_HEIGHT) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedQsPadding = STABLE_INSET_BOTTOM) + } + + @Test + fun testCustomizingInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + notificationsQSContainerController.setCustomizerShowing(true) + // always sets spacings to 0 + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + } + + @Test + fun testDetailShowingInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + notificationsQSContainerController.setDetailShowing(true) + // always sets spacings to 0 + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + } + + @Test + fun testDetailShowingInSplitShade() { + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + notificationsQSContainerController.setDetailShowing(true) + // should not influence spacing + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0) + } + + private fun given( + taskbarVisible: Boolean, + navigationMode: Int, + insets: WindowInsets + ) { + Mockito.clearInvocations(notificationsQSContainer) + taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false) + navigationModeCallback.onNavigationModeChanged(navigationMode) + windowInsetsCallback.accept(insets) + } + + fun then( + expectedContainerPadding: Int, + expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN, + expectedQsPadding: Int = 0 + ) { + verify(notificationsQSContainer) + .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding)) + verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin) + verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding) + Mockito.clearInvocations(notificationsQSContainer) + } + + private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS) + + private fun emptyInsets() = mock(WindowInsets::class.java) + + private fun WindowInsets.withCutout(): WindowInsets { + whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT) + return this + } + + private fun WindowInsets.withStableBottom(): WindowInsets { + whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM) + return this + } +}
\ No newline at end of file |