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 }