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.systemui.dagger.SysUISingleton 25 import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener 26 import com.android.systemui.util.Compile 27 import java.util.concurrent.CopyOnWriteArrayList 28 import javax.inject.Inject 29 30 /** 31 * A class responsible for managing the notification panel's current state. 32 * 33 * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion. 34 */ 35 @SysUISingleton 36 class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { 37 38 private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>() 39 private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>() 40 private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>() 41 private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>() 42 private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>() 43 44 @PanelState private var state: Int = STATE_CLOSED 45 @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f 46 private var expanded: Boolean = false 47 private var qsExpanded: Boolean = false 48 private var tracking: Boolean = false 49 private var dragDownPxAmount: Float = 0f 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/23035507). 54 * 55 * @see #addExpansionListener 56 */ 57 fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent { 58 expansionListeners.add(listener) 59 return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount) 60 } 61 62 /** Removes an expansion listener. */ 63 fun removeExpansionListener(listener: ShadeExpansionListener) { 64 expansionListeners.remove(listener) 65 } 66 67 fun addFullExpansionListener(listener: ShadeFullExpansionListener) { 68 fullExpansionListeners.add(listener) 69 listener.onShadeExpansionFullyChanged(qsExpanded) 70 } 71 72 fun removeFullExpansionListener(listener: ShadeFullExpansionListener) { 73 fullExpansionListeners.remove(listener) 74 } 75 76 fun addQsExpansionListener(listener: ShadeQsExpansionListener) { 77 qsExpansionListeners.add(listener) 78 listener.onQsExpansionChanged(qsExpanded) 79 } 80 81 fun removeQsExpansionListener(listener: ShadeQsExpansionListener) { 82 qsExpansionListeners.remove(listener) 83 } 84 85 /** Adds a listener that will be notified when the panel state has changed. */ 86 fun addStateListener(listener: ShadeStateListener) { 87 stateListeners.add(listener) 88 } 89 90 /** Removes a state listener. */ 91 fun removeStateListener(listener: ShadeStateListener) { 92 stateListeners.remove(listener) 93 } 94 95 override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) { 96 shadeStateEventsListeners.addIfAbsent(listener) 97 } 98 99 override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) { 100 shadeStateEventsListeners.remove(listener) 101 } 102 103 /** Returns true if the panel is currently closed and false otherwise. */ 104 fun isClosed(): Boolean = state == STATE_CLOSED 105 106 /** 107 * Called when the panel expansion has changed. 108 * 109 * @param fraction the fraction from the expansion in [0, 1] 110 * @param expanded whether the panel is currently expanded; this is independent from the 111 * fraction as the panel also might be expanded if the fraction is 0. 112 * @param tracking whether we're currently tracking the user's gesture. 113 */ 114 fun onPanelExpansionChanged( 115 @FloatRange(from = 0.0, to = 1.0) fraction: Float, 116 expanded: Boolean, 117 tracking: Boolean, 118 dragDownPxAmount: Float 119 ) { 120 require(!fraction.isNaN()) { "fraction cannot be NaN" } 121 val oldState = state 122 123 this.fraction = fraction 124 this.expanded = expanded 125 this.tracking = tracking 126 this.dragDownPxAmount = dragDownPxAmount 127 128 var fullyClosed = true 129 var fullyOpened = false 130 131 if (expanded) { 132 if (this.state == STATE_CLOSED) { 133 updateStateInternal(STATE_OPENING) 134 } 135 fullyClosed = false 136 fullyOpened = fraction >= 1f 137 } 138 139 if (fullyOpened && !tracking) { 140 updateStateInternal(STATE_OPEN) 141 } else if (fullyClosed && !tracking && this.state != STATE_CLOSED) { 142 updateStateInternal(STATE_CLOSED) 143 } 144 145 debugLog( 146 "panelExpansionChanged:" + 147 "start state=${oldState.panelStateToString()} " + 148 "end state=${state.panelStateToString()} " + 149 "f=$fraction " + 150 "expanded=$expanded " + 151 "tracking=$tracking " + 152 "dragDownPxAmount=$dragDownPxAmount " + 153 "${if (fullyOpened) " fullyOpened" else ""} " + 154 if (fullyClosed) " fullyClosed" else "" 155 ) 156 157 if (Trace.isTagEnabled(TRACE_TAG)) { 158 Trace.traceCounter(TRACE_TAG, "panel_expansion", (fraction * 100).toInt()) 159 if (state != oldState) { 160 Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK_NAME, 0) 161 Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK_NAME, state.panelStateToString(), 0) 162 } 163 } 164 165 val expansionChangeEvent = 166 ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount) 167 expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) } 168 } 169 170 /** Called when the quick settings expansion changes to fully expanded or collapsed. */ 171 fun onQsExpansionChanged(qsExpanded: Boolean) { 172 this.qsExpanded = qsExpanded 173 174 debugLog("qsExpanded=$qsExpanded") 175 qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) } 176 } 177 178 fun onShadeExpansionFullyChanged(isExpanded: Boolean) { 179 this.expanded = isExpanded 180 181 debugLog("expanded=$isExpanded") 182 fullExpansionListeners.forEach { it.onShadeExpansionFullyChanged(isExpanded) } 183 } 184 185 /** Updates the panel state if necessary. */ 186 fun updateState(@PanelState state: Int) { 187 debugLog( 188 "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}" 189 ) 190 if (this.state != state) { 191 updateStateInternal(state) 192 } 193 } 194 195 private fun updateStateInternal(@PanelState state: Int) { 196 debugLog("go state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}") 197 this.state = state 198 stateListeners.forEach { it.onPanelStateChanged(state) } 199 } 200 201 fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) { 202 for (cb in shadeStateEventsListeners) { 203 cb.onLaunchingActivityChanged(isLaunchingActivity) 204 } 205 } 206 207 fun notifyPanelCollapsingChanged(isCollapsing: Boolean) { 208 for (cb in shadeStateEventsListeners) { 209 cb.onPanelCollapsingChanged(isCollapsing) 210 } 211 } 212 213 fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) { 214 for (cb in shadeStateEventsListeners) { 215 cb.onExpandImmediateChanged(expandImmediateEnabled) 216 } 217 } 218 219 private fun debugLog(msg: String) { 220 if (!DEBUG) return 221 Log.v(TAG, msg) 222 } 223 224 companion object { 225 private const val TRACK_NAME = "ShadeExpansionState" 226 } 227 } 228 229 /** Enum for the current state of the panel. */ 230 @Retention(AnnotationRetention.SOURCE) 231 @IntDef(value = [STATE_CLOSED, STATE_OPENING, STATE_OPEN]) 232 internal annotation class PanelState 233 234 const val STATE_CLOSED = 0 235 const val STATE_OPENING = 1 236 const val STATE_OPEN = 2 237 238 @PanelState 239 fun Int.panelStateToString(): String { 240 return when (this) { 241 STATE_CLOSED -> "CLOSED" 242 STATE_OPENING -> "OPENING" 243 STATE_OPEN -> "OPEN" 244 else -> this.toString() 245 } 246 } 247 248 private val TAG = ShadeExpansionStateManager::class.simpleName 249 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG) 250