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