1 package com.android.systemui.statusbar.phone 2 3 import android.view.WindowInsets 4 import com.android.systemui.navigationbar.NavigationModeController 5 import com.android.systemui.plugins.qs.QS 6 import com.android.systemui.plugins.qs.QSContainerController 7 import com.android.systemui.recents.OverviewProxyService 8 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener 9 import com.android.systemui.shared.system.QuickStepContract 10 import com.android.systemui.util.ViewController 11 import java.util.function.Consumer 12 import javax.inject.Inject 13 14 class NotificationsQSContainerController @Inject constructor( 15 view: NotificationsQuickSettingsContainer, 16 private val navigationModeController: NavigationModeController, 17 private val overviewProxyService: OverviewProxyService 18 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { 19 20 var qsExpanded = false 21 set(value) { 22 if (field != value) { 23 field = value 24 mView.invalidate() 25 } 26 } 27 var splitShadeEnabled = false 28 set(value) { 29 if (field != value) { 30 field = value 31 // in case device configuration changed while showing QS details/customizer 32 updateBottomSpacing() 33 } 34 } 35 36 private var isQSDetailShowing = false 37 private var isQSCustomizing = false 38 private var isQSCustomizerAnimating = false 39 40 private var notificationsBottomMargin = 0 41 private var bottomStableInsets = 0 42 private var bottomCutoutInsets = 0 43 44 private var isGestureNavigation = true 45 private var taskbarVisible = false 46 private val taskbarVisibilityListener: OverviewProxyListener = object : OverviewProxyListener { 47 override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) { 48 taskbarVisible = visible 49 } 50 } 51 private val windowInsetsListener: Consumer<WindowInsets> = Consumer { insets -> 52 // when taskbar is visible, stableInsetBottom will include its height 53 bottomStableInsets = insets.stableInsetBottom 54 bottomCutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0 55 updateBottomSpacing() 56 } 57 58 override fun onInit() { 59 val currentMode: Int = navigationModeController.addListener { mode: Int -> 60 isGestureNavigation = QuickStepContract.isGesturalMode(mode) 61 } 62 isGestureNavigation = QuickStepContract.isGesturalMode(currentMode) 63 } 64 65 public override fun onViewAttached() { 66 notificationsBottomMargin = mView.defaultNotificationsMarginBottom 67 overviewProxyService.addCallback(taskbarVisibilityListener) 68 mView.setInsetsChangedListener(windowInsetsListener) 69 mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } 70 } 71 72 override fun onViewDetached() { 73 overviewProxyService.removeCallback(taskbarVisibilityListener) 74 mView.removeOnInsetsChangedListener() 75 mView.removeQSFragmentAttachedListener() 76 } 77 78 override fun setCustomizerAnimating(animating: Boolean) { 79 if (isQSCustomizerAnimating != animating) { 80 isQSCustomizerAnimating = animating 81 mView.invalidate() 82 } 83 } 84 85 override fun setCustomizerShowing(showing: Boolean) { 86 isQSCustomizing = showing 87 updateBottomSpacing() 88 } 89 90 override fun setDetailShowing(showing: Boolean) { 91 isQSDetailShowing = showing 92 updateBottomSpacing() 93 } 94 95 private fun updateBottomSpacing() { 96 val (containerPadding, notificationsMargin) = calculateBottomSpacing() 97 var qsScrollPaddingBottom = 0 98 if (!(splitShadeEnabled || isQSCustomizing || isQSDetailShowing || isGestureNavigation || 99 taskbarVisible)) { 100 // no taskbar, portrait, navigation buttons enabled: 101 // padding is needed so QS can scroll up over bottom insets - to reach the point when 102 // the whole QS is above bottom insets 103 qsScrollPaddingBottom = bottomStableInsets 104 } 105 mView.setPadding(0, 0, 0, containerPadding) 106 mView.setNotificationsMarginBottom(notificationsMargin) 107 mView.setQSScrollPaddingBottom(qsScrollPaddingBottom) 108 } 109 110 private fun calculateBottomSpacing(): Pair<Int, Int> { 111 val containerPadding: Int 112 var stackScrollMargin = notificationsBottomMargin 113 if (splitShadeEnabled) { 114 if (isGestureNavigation) { 115 // only default cutout padding, taskbar always hides 116 containerPadding = bottomCutoutInsets 117 } else if (taskbarVisible) { 118 // navigation buttons + visible taskbar means we're NOT on homescreen 119 containerPadding = bottomStableInsets 120 } else { 121 // navigation buttons + hidden taskbar means we're on homescreen 122 containerPadding = 0 123 // we need extra margin for notifications as navigation buttons are below them 124 stackScrollMargin = bottomStableInsets + notificationsBottomMargin 125 } 126 } else { 127 if (isQSCustomizing || isQSDetailShowing) { 128 // Clear out bottom paddings/margins so the qs customization can be full height. 129 containerPadding = 0 130 stackScrollMargin = 0 131 } else if (isGestureNavigation) { 132 containerPadding = bottomCutoutInsets 133 } else if (taskbarVisible) { 134 containerPadding = bottomStableInsets 135 } else { 136 containerPadding = 0 137 } 138 } 139 return containerPadding to stackScrollMargin 140 } 141 }