blob: 533927d5bf0a9d37d88099548b51747dae0fd2d0 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.shade
import android.annotation.IntDef
import android.os.Trace
import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
import com.android.systemui.util.Compile
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
/**
* A class responsible for managing the notification panel's current state.
*
* TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
*/
@SysUISingleton
class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
private val qsExpansionFractionListeners =
CopyOnWriteArrayList<ShadeQsExpansionFractionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
private var expanded: Boolean = false
private var qsExpanded: Boolean = false
private var qsExpansionFraction = 0f
private var tracking: Boolean = false
private var dragDownPxAmount: Float = 0f
/**
* Adds a listener that will be notified when the panel expansion fraction has changed and
* returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/23035507).
*
* @see #addExpansionListener
*/
fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent {
expansionListeners.add(listener)
return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
}
/** Removes an expansion listener. */
fun removeExpansionListener(listener: ShadeExpansionListener) {
expansionListeners.remove(listener)
}
fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
qsExpansionListeners.add(listener)
listener.onQsExpansionChanged(qsExpanded)
}
fun removeQsExpansionListener(listener: ShadeQsExpansionListener) {
qsExpansionListeners.remove(listener)
}
fun addQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
qsExpansionFractionListeners.add(listener)
listener.onQsExpansionFractionChanged(qsExpansionFraction)
}
fun removeQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
qsExpansionFractionListeners.remove(listener)
}
/** Adds a listener that will be notified when the panel state has changed. */
fun addStateListener(listener: ShadeStateListener) {
stateListeners.add(listener)
}
override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
shadeStateEventsListeners.addIfAbsent(listener)
}
override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) {
shadeStateEventsListeners.remove(listener)
}
/** Returns true if the panel is currently closed and false otherwise. */
fun isClosed(): Boolean = state == STATE_CLOSED
/**
* Called when the panel expansion has changed.
*
* @param fraction the fraction from the expansion in [0, 1]
* @param expanded whether the panel is currently expanded; this is independent from the
* fraction as the panel also might be expanded if the fraction is 0.
* @param tracking whether we're currently tracking the user's gesture.
*/
fun onPanelExpansionChanged(
@FloatRange(from = 0.0, to = 1.0) fraction: Float,
expanded: Boolean,
tracking: Boolean,
dragDownPxAmount: Float
) {
require(!fraction.isNaN()) { "fraction cannot be NaN" }
val oldState = state
this.fraction = fraction
this.expanded = expanded
this.tracking = tracking
this.dragDownPxAmount = dragDownPxAmount
var fullyClosed = true
var fullyOpened = false
if (expanded) {
if (this.state == STATE_CLOSED) {
updateStateInternal(STATE_OPENING)
}
fullyClosed = false
fullyOpened = fraction >= 1f
}
if (fullyOpened && !tracking) {
updateStateInternal(STATE_OPEN)
} else if (fullyClosed && !tracking && this.state != STATE_CLOSED) {
updateStateInternal(STATE_CLOSED)
}
debugLog(
"panelExpansionChanged:" +
"start state=${oldState.panelStateToString()} " +
"end state=${state.panelStateToString()} " +
"f=$fraction " +
"expanded=$expanded " +
"tracking=$tracking " +
"dragDownPxAmount=$dragDownPxAmount " +
"${if (fullyOpened) " fullyOpened" else ""} " +
if (fullyClosed) " fullyClosed" else ""
)
if (Trace.isTagEnabled(TRACE_TAG)) {
Trace.traceCounter(TRACE_TAG, "panel_expansion", (fraction * 100).toInt())
if (state != oldState) {
Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK_NAME, 0)
Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK_NAME, state.panelStateToString(), 0)
}
}
val expansionChangeEvent =
ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
}
/** Called when the quick settings expansion changes to fully expanded or collapsed. */
fun onQsExpansionChanged(qsExpanded: Boolean) {
this.qsExpanded = qsExpanded
debugLog("qsExpanded=$qsExpanded")
qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
}
fun onQsExpansionFractionChanged(qsExpansionFraction: Float) {
this.qsExpansionFraction = qsExpansionFraction
debugLog("qsExpansionFraction=$qsExpansionFraction")
qsExpansionFractionListeners.forEach {
it.onQsExpansionFractionChanged(qsExpansionFraction)
}
}
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
debugLog(
"update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}"
)
if (this.state != state) {
updateStateInternal(state)
}
}
private fun updateStateInternal(@PanelState state: Int) {
debugLog("go state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
this.state = state
stateListeners.forEach { it.onPanelStateChanged(state) }
}
fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) {
for (cb in shadeStateEventsListeners) {
cb.onLaunchingActivityChanged(isLaunchingActivity)
}
}
fun notifyPanelCollapsingChanged(isCollapsing: Boolean) {
for (cb in shadeStateEventsListeners) {
cb.onPanelCollapsingChanged(isCollapsing)
}
}
fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) {
for (cb in shadeStateEventsListeners) {
cb.onExpandImmediateChanged(expandImmediateEnabled)
}
}
private fun debugLog(msg: String) {
if (!DEBUG) return
Log.v(TAG, msg)
}
companion object {
private const val TRACK_NAME = "ShadeExpansionState"
}
}
/** Enum for the current state of the panel. */
@Retention(AnnotationRetention.SOURCE)
@IntDef(value = [STATE_CLOSED, STATE_OPENING, STATE_OPEN])
internal annotation class PanelState
const val STATE_CLOSED = 0
const val STATE_OPENING = 1
const val STATE_OPEN = 2
@PanelState
fun Int.panelStateToString(): String {
return when (this) {
STATE_CLOSED -> "CLOSED"
STATE_OPENING -> "OPENING"
STATE_OPEN -> "OPEN"
else -> this.toString()
}
}
private val TAG = ShadeExpansionStateManager::class.simpleName
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)