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.statusbar.phone.panelstate 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 javax.inject.Inject 24 25 /** 26 * A class responsible for managing the notification panel's current state. 27 * 28 * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion. 29 */ 30 @SysUISingleton 31 class PanelExpansionStateManager @Inject constructor() { 32 33 private val expansionListeners = mutableListOf<PanelExpansionListener>() 34 private val stateListeners = mutableListOf<PanelStateListener>() 35 36 @PanelState private var state: Int = STATE_CLOSED 37 @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f 38 private var expanded: Boolean = false 39 private var tracking: Boolean = false 40 41 /** 42 * Adds a listener that will be notified when the panel expansion fraction has changed. 43 * 44 * Listener will also be immediately notified with the current values. 45 */ 46 fun addExpansionListener(listener: PanelExpansionListener) { 47 expansionListeners.add(listener) 48 listener.onPanelExpansionChanged(fraction, expanded, tracking) 49 } 50 51 /** Removes an expansion listener. */ 52 fun removeExpansionListener(listener: PanelExpansionListener) { 53 expansionListeners.remove(listener) 54 } 55 56 /** Adds a listener that will be notified when the panel state has changed. */ 57 fun addStateListener(listener: PanelStateListener) { 58 stateListeners.add(listener) 59 } 60 61 /** Removes a state listener. */ 62 fun removeStateListener(listener: PanelStateListener) { 63 stateListeners.remove(listener) 64 } 65 66 /** Returns true if the panel is currently closed and false otherwise. */ 67 fun isClosed(): Boolean = state == STATE_CLOSED 68 69 /** 70 * Called when the panel expansion has changed. 71 * 72 * @param fraction the fraction from the expansion in [0, 1] 73 * @param expanded whether the panel is currently expanded; this is independent from the 74 * fraction as the panel also might be expanded if the fraction is 0. 75 * @param tracking whether we're currently tracking the user's gesture. 76 */ 77 fun onPanelExpansionChanged( 78 @FloatRange(from = 0.0, to = 1.0) fraction: Float, 79 expanded: Boolean, 80 tracking: Boolean 81 ) { 82 require(!fraction.isNaN()) { "fraction cannot be NaN" } 83 val oldState = state 84 85 this.fraction = fraction 86 this.expanded = expanded 87 this.tracking = tracking 88 89 var fullyClosed = true 90 var fullyOpened = false 91 92 if (expanded) { 93 if (this.state == STATE_CLOSED) { 94 updateStateInternal(STATE_OPENING) 95 } 96 fullyClosed = false 97 fullyOpened = fraction >= 1f 98 } 99 100 if (fullyOpened && !tracking) { 101 updateStateInternal(STATE_OPEN) 102 } else if (fullyClosed && !tracking && this.state != STATE_CLOSED) { 103 updateStateInternal(STATE_CLOSED) 104 } 105 106 debugLog( 107 "panelExpansionChanged:" + 108 "start state=${oldState.stateToString()} " + 109 "end state=${state.stateToString()} " + 110 "f=$fraction " + 111 "expanded=$expanded " + 112 "tracking=$tracking" + 113 "${if (fullyOpened) " fullyOpened" else ""} " + 114 if (fullyClosed) " fullyClosed" else "" 115 ) 116 117 expansionListeners.forEach { it.onPanelExpansionChanged(fraction, expanded, tracking) } 118 } 119 120 /** Updates the panel state if necessary. */ 121 fun updateState(@PanelState state: Int) { 122 debugLog("update state: ${this.state.stateToString()} -> ${state.stateToString()}") 123 if (this.state != state) { 124 updateStateInternal(state) 125 } 126 } 127 128 private fun updateStateInternal(@PanelState state: Int) { 129 debugLog("go state: ${this.state.stateToString()} -> ${state.stateToString()}") 130 this.state = state 131 stateListeners.forEach { it.onPanelStateChanged(state) } 132 } 133 134 private fun debugLog(msg: String) { 135 if (!DEBUG) return 136 Log.v(TAG, msg) 137 } 138 } 139 140 /** Enum for the current state of the panel. */ 141 @Retention(AnnotationRetention.SOURCE) 142 @IntDef(value = [STATE_CLOSED, STATE_OPENING, STATE_OPEN]) 143 internal annotation class PanelState 144 145 const val STATE_CLOSED = 0 146 const val STATE_OPENING = 1 147 const val STATE_OPEN = 2 148 149 @PanelState 150 private fun Int.stateToString(): String { 151 return when (this) { 152 STATE_CLOSED -> "CLOSED" 153 STATE_OPENING -> "OPENING" 154 STATE_OPEN -> "OPEN" 155 else -> this.toString() 156 } 157 } 158 159 private const val DEBUG = false 160 private val TAG = PanelExpansionStateManager::class.simpleName 161