1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.systemui.shade
18
19 import android.annotation.IntDef
20 import android.util.Log
21 import androidx.annotation.FloatRange
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
24 import com.android.systemui.util.Compile
25 import java.util.concurrent.CopyOnWriteArrayList
26 import javax.inject.Inject
27
28 /**
29 * A class responsible for managing the notification panel's current state.
30 *
31 * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
32 */
33 @SysUISingleton
34 class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
35
36 private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
37 private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>()
38 private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
39 private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
40 private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
41
42 @PanelState private var state: Int = STATE_CLOSED
43 @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
44 private var expanded: Boolean = false
45 private var qsExpanded: Boolean = false
46 private var tracking: Boolean = false
47 private var dragDownPxAmount: Float = 0f
48
49 /**
50 * Adds a listener that will be notified when the panel expansion fraction has changed.
51 *
52 * Listener will also be immediately notified with the current values.
53 */
addExpansionListenernull54 fun addExpansionListener(listener: ShadeExpansionListener) {
55 expansionListeners.add(listener)
56 listener.onPanelExpansionChanged(
57 ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
58 )
59 }
60
61 /** Removes an expansion listener. */
removeExpansionListenernull62 fun removeExpansionListener(listener: ShadeExpansionListener) {
63 expansionListeners.remove(listener)
64 }
65
addFullExpansionListenernull66 fun addFullExpansionListener(listener: ShadeFullExpansionListener) {
67 fullExpansionListeners.add(listener)
68 listener.onShadeExpansionFullyChanged(qsExpanded)
69 }
70
removeFullExpansionListenernull71 fun removeFullExpansionListener(listener: ShadeFullExpansionListener) {
72 fullExpansionListeners.remove(listener)
73 }
74
addQsExpansionListenernull75 fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
76 qsExpansionListeners.add(listener)
77 listener.onQsExpansionChanged(qsExpanded)
78 }
79
removeQsExpansionListenernull80 fun removeQsExpansionListener(listener: ShadeQsExpansionListener) {
81 qsExpansionListeners.remove(listener)
82 }
83
84 /** Adds a listener that will be notified when the panel state has changed. */
addStateListenernull85 fun addStateListener(listener: ShadeStateListener) {
86 stateListeners.add(listener)
87 }
88
89 /** Removes a state listener. */
removeStateListenernull90 fun removeStateListener(listener: ShadeStateListener) {
91 stateListeners.remove(listener)
92 }
93
addShadeStateEventsListenernull94 override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
95 shadeStateEventsListeners.addIfAbsent(listener)
96 }
97
removeShadeStateEventsListenernull98 override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) {
99 shadeStateEventsListeners.remove(listener)
100 }
101
102 /** Returns true if the panel is currently closed and false otherwise. */
isClosednull103 fun isClosed(): Boolean = state == STATE_CLOSED
104
105 /**
106 * Called when the panel expansion has changed.
107 *
108 * @param fraction the fraction from the expansion in [0, 1]
109 * @param expanded whether the panel is currently expanded; this is independent from the
110 * fraction as the panel also might be expanded if the fraction is 0.
111 * @param tracking whether we're currently tracking the user's gesture.
112 */
113 fun onPanelExpansionChanged(
114 @FloatRange(from = 0.0, to = 1.0) fraction: Float,
115 expanded: Boolean,
116 tracking: Boolean,
117 dragDownPxAmount: Float
118 ) {
119 require(!fraction.isNaN()) { "fraction cannot be NaN" }
120 val oldState = state
121
122 this.fraction = fraction
123 this.expanded = expanded
124 this.tracking = tracking
125 this.dragDownPxAmount = dragDownPxAmount
126
127 var fullyClosed = true
128 var fullyOpened = false
129
130 if (expanded) {
131 if (this.state == STATE_CLOSED) {
132 updateStateInternal(STATE_OPENING)
133 }
134 fullyClosed = false
135 fullyOpened = fraction >= 1f
136 }
137
138 if (fullyOpened && !tracking) {
139 updateStateInternal(STATE_OPEN)
140 } else if (fullyClosed && !tracking && this.state != STATE_CLOSED) {
141 updateStateInternal(STATE_CLOSED)
142 }
143
144 debugLog(
145 "panelExpansionChanged:" +
146 "start state=${oldState.panelStateToString()} " +
147 "end state=${state.panelStateToString()} " +
148 "f=$fraction " +
149 "expanded=$expanded " +
150 "tracking=$tracking " +
151 "dragDownPxAmount=$dragDownPxAmount " +
152 "${if (fullyOpened) " fullyOpened" else ""} " +
153 if (fullyClosed) " fullyClosed" else ""
154 )
155
156 val expansionChangeEvent =
157 ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
158 expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
159 }
160
161 /** Called when the quick settings expansion changes to fully expanded or collapsed. */
onQsExpansionChangednull162 fun onQsExpansionChanged(qsExpanded: Boolean) {
163 this.qsExpanded = qsExpanded
164
165 debugLog("qsExpanded=$qsExpanded")
166 qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
167 }
168
onShadeExpansionFullyChangednull169 fun onShadeExpansionFullyChanged(isExpanded: Boolean) {
170 this.expanded = isExpanded
171
172 debugLog("expanded=$isExpanded")
173 fullExpansionListeners.forEach { it.onShadeExpansionFullyChanged(isExpanded) }
174 }
175
176 /** Updates the panel state if necessary. */
updateStatenull177 fun updateState(@PanelState state: Int) {
178 debugLog(
179 "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}"
180 )
181 if (this.state != state) {
182 updateStateInternal(state)
183 }
184 }
185
updateStateInternalnull186 private fun updateStateInternal(@PanelState state: Int) {
187 debugLog("go state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
188 this.state = state
189 stateListeners.forEach { it.onPanelStateChanged(state) }
190 }
191
notifyLaunchingActivityChangednull192 fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) {
193 for (cb in shadeStateEventsListeners) {
194 cb.onLaunchingActivityChanged(isLaunchingActivity)
195 }
196 }
197
notifyPanelCollapsingChangednull198 fun notifyPanelCollapsingChanged(isCollapsing: Boolean) {
199 for (cb in shadeStateEventsListeners) {
200 cb.onPanelCollapsingChanged(isCollapsing)
201 }
202 }
203
notifyExpandImmediateChangenull204 fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) {
205 for (cb in shadeStateEventsListeners) {
206 cb.onExpandImmediateChanged(expandImmediateEnabled)
207 }
208 }
209
debugLognull210 private fun debugLog(msg: String) {
211 if (!DEBUG) return
212 Log.v(TAG, msg)
213 }
214 }
215
216 /** Enum for the current state of the panel. */
217 @Retention(AnnotationRetention.SOURCE)
218 @IntDef(value = [STATE_CLOSED, STATE_OPENING, STATE_OPEN])
219 internal annotation class PanelState
220
221 const val STATE_CLOSED = 0
222 const val STATE_OPENING = 1
223 const val STATE_OPEN = 2
224
225 @PanelState
panelStateToStringnull226 fun Int.panelStateToString(): String {
227 return when (this) {
228 STATE_CLOSED -> "CLOSED"
229 STATE_OPENING -> "OPENING"
230 STATE_OPEN -> "OPEN"
231 else -> this.toString()
232 }
233 }
234
235 private val TAG = ShadeExpansionStateManager::class.simpleName
236 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
237