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.os.Trace
21 import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
22 import android.util.Log
23 import androidx.annotation.FloatRange
24 import com.android.app.tracing.TraceStateLogger
25 import com.android.app.tracing.TrackGroupUtils.trackGroup
26 import com.android.app.tracing.coroutines.TrackTracer
27 import com.android.systemui.dagger.SysUISingleton
28 import com.android.systemui.util.Compile
29 import java.util.concurrent.CopyOnWriteArrayList
30 import javax.inject.Inject
31
32 /**
33 * A class responsible for managing the notification panel's current state.
34 *
35 * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
36 */
37 @SysUISingleton
38 @Deprecated("Use ShadeInteractor instead")
39 class ShadeExpansionStateManager @Inject constructor() {
40
41 private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
42 private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
43
44 private val stateLogger = TraceStateLogger(trackGroup("shade", TRACK_NAME))
45
46 @PanelState private var state: Int = STATE_CLOSED
47 @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
48 private var expanded: Boolean = false
49 private var tracking: Boolean = false
50
51 /**
52 * Adds a listener that will be notified when the panel expansion fraction has changed and
53 * returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/281038056).
54 *
55 * @see #addExpansionListener
56 */
57 @Deprecated("Use ShadeInteractor instead")
addExpansionListenernull58 fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent {
59 expansionListeners.add(listener)
60 return ShadeExpansionChangeEvent(fraction, expanded, tracking)
61 }
62
63 /** Adds a listener that will be notified when the panel state has changed. */
64 @Deprecated("Use ShadeInteractor instead")
addStateListenernull65 fun addStateListener(listener: ShadeStateListener) {
66 stateListeners.add(listener)
67 }
68
69 /** Returns true if the panel is currently closed and false otherwise. */
isClosednull70 fun isClosed(): Boolean = state == STATE_CLOSED
71
72 /**
73 * Called when the panel expansion has changed.
74 *
75 * @param fraction the fraction from the expansion in [0, 1]
76 * @param expanded whether the panel is currently expanded; this is independent from the
77 * fraction as the panel also might be expanded if the fraction is 0.
78 * @param tracking whether we're currently tracking the user's gesture.
79 */
80 fun onPanelExpansionChanged(
81 @FloatRange(from = 0.0, to = 1.0) fraction: Float,
82 expanded: Boolean,
83 tracking: Boolean,
84 ) {
85 require(!fraction.isNaN()) { "fraction cannot be NaN" }
86 val oldState = state
87
88 this.fraction = fraction
89 this.expanded = expanded
90 this.tracking = tracking
91
92 var fullyClosed = true
93 var fullyOpened = false
94
95 if (expanded) {
96 if (this.state == STATE_CLOSED) {
97 updateStateInternal(STATE_OPENING)
98 }
99 fullyClosed = false
100 fullyOpened = fraction >= 1f
101 }
102
103 if (fullyOpened && !tracking) {
104 updateStateInternal(STATE_OPEN)
105 } else if (fullyClosed && !tracking && this.state != STATE_CLOSED) {
106 updateStateInternal(STATE_CLOSED)
107 }
108
109 debugLog(
110 "panelExpansionChanged:" +
111 "start state=${oldState.panelStateToString()} " +
112 "end state=${state.panelStateToString()} " +
113 "f=$fraction " +
114 "expanded=$expanded " +
115 "tracking=$tracking " +
116 "${if (fullyOpened) " fullyOpened" else ""} " +
117 if (fullyClosed) " fullyClosed" else ""
118 )
119
120 if (Trace.isTagEnabled(TRACE_TAG)) {
121 TrackTracer.instantForGroup("shade", "panel_expansion", fraction)
122 stateLogger.log(state.panelStateToString())
123 }
124
125 val expansionChangeEvent = ShadeExpansionChangeEvent(fraction, expanded, tracking)
126 expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
127 }
128
129 /** Updates the panel state if necessary. */
updateStatenull130 fun updateState(@PanelState state: Int) {
131 debugLog(
132 "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}"
133 )
134 if (this.state != state) {
135 updateStateInternal(state)
136 }
137 }
138
updateStateInternalnull139 private fun updateStateInternal(@PanelState state: Int) {
140 debugLog("go state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
141 this.state = state
142 stateListeners.forEach { it.onPanelStateChanged(state) }
143 }
144
debugLognull145 private fun debugLog(msg: String) {
146 if (!DEBUG) return
147 Log.v(TAG, msg)
148 }
149
150 companion object {
151 private const val TRACK_NAME = "ShadeExpansionState"
152 }
153 }
154
155 /** Enum for the current state of the panel. */
156 @Retention(AnnotationRetention.SOURCE)
157 @IntDef(value = [STATE_CLOSED, STATE_OPENING, STATE_OPEN])
158 internal annotation class PanelState
159
160 const val STATE_CLOSED = 0
161 const val STATE_OPENING = 1
162 const val STATE_OPEN = 2
163
164 @PanelState
panelStateToStringnull165 fun Int.panelStateToString(): String {
166 return when (this) {
167 STATE_CLOSED -> "CLOSED"
168 STATE_OPENING -> "OPENING"
169 STATE_OPEN -> "OPEN"
170 else -> this.toString()
171 }
172 }
173
174 private val TAG = ShadeExpansionStateManager::class.simpleName
175 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
176