Merge "Fixing bottom part of QS not getting any touches in split shade" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3b59f276f5
@@ -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();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.android.systemui.plugins.qs
|
||||
|
||||
interface QSContainerController {
|
||||
fun setCustomizerAnimating(animating: Boolean)
|
||||
|
||||
fun setCustomizerShowing(showing: Boolean)
|
||||
|
||||
fun setDetailShowing(showing: Boolean)
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -94,25 +92,14 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
|
||||
return true;
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -68,6 +67,59 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
|
||||
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
|
||||
}
|
||||
|
||||
@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();
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user