• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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