1 /*
2 * Copyright (C) 2025 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 package com.android.systemui.model
17
18 import android.util.Log
19 import android.view.Display
20 import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
21 import com.android.systemui.Dumpable
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dump.DumpManager
24 import com.android.systemui.model.SysUiState.SysUiStateCallback
25 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
26 import com.android.systemui.shared.system.QuickStepContract
27 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
28 import dagger.assisted.Assisted
29 import dagger.assisted.AssistedFactory
30 import dagger.assisted.AssistedInject
31 import dalvik.annotation.optimization.NeverCompile
32 import java.io.PrintWriter
33 import java.lang.Long.bitCount
34 import javax.inject.Inject
35
36 /** Contains sysUi state flags and notifies registered listeners whenever changes happen. */
37 interface SysUiState : Dumpable {
38 /**
39 * Add listener to be notified of changes made to SysUI state.
40 *
41 * The callback will also be called as part of this function.
42 */
addCallbacknull43 fun addCallback(callback: SysUiStateCallback)
44
45 /** Removes a callback for state changes. */
46 fun removeCallback(callback: SysUiStateCallback)
47
48 /** Returns whether a flag is enabled in this state. */
49 fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean {
50 return (flags and flag) != 0L
51 }
52
53 /** Returns the current sysui state flags. */
54 val flags: Long
55
56 /** Methods to this call can be chained together before calling [commitUpdate]. */
setFlagnull57 fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState
58
59 /** Call to save all the flags updated from [setFlag]. */
60 @Deprecated("Each SysUIState instance is now display specific. Just use commitUpdate()")
61 fun commitUpdate(displayId: Int)
62
63 /** Call to save all the flags updated from [setFlag]. */
64 fun commitUpdate()
65
66 /** Callback to be notified whenever system UI state flags are changed. */
67 fun interface SysUiStateCallback {
68
69 /** To be called when any SysUiStateFlag gets updated for a specific [displayId]. */
70 fun onSystemUiStateChanged(@SystemUiStateFlags sysUiFlags: Long, displayId: Int)
71 }
72
73 /**
74 * Destroys an instance. It shouldn't be used anymore afterwards.
75 *
76 * This is mainly used to clean up instances associated with displays that are removed.
77 */
destroynull78 fun destroy()
79
80 /** Initializes the state after construction. */
81 fun start()
82
83 /** The display ID this instances is associated with */
84 val displayId: Int
85
86 companion object {
87 const val DEBUG: Boolean = false
88 }
89 }
90
91 private const val TAG = "SysUIState"
92
93 open class SysUiStateImpl
94 @AssistedInject
95 constructor(
96 @Assisted override val displayId: Int,
97 private val sceneContainerPlugin: SceneContainerPlugin?,
98 private val dumpManager: DumpManager,
99 private val stateDispatcher: SysUIStateDispatcher,
100 ) : SysUiState {
101
102 private val debugName
103 get() = "SysUiStateImpl-ForDisplay=$displayId"
104
startnull105 override fun start() {
106 dumpManager.registerNormalDumpable(debugName, this)
107 }
108
109 /** Returns the current sysui state flags. */
110 @get:SystemUiStateFlags
111 @SystemUiStateFlags
112 override val flags: Long
113 get() = _flags
114
115 private var _flags: Long = 0
116 private val stateChange = StateChange()
117
118 /**
119 * Add listener to be notified of changes made to SysUI state. The callback will also be called
120 * as part of this function.
121 *
122 * Note that the listener would receive updates for all displays.
123 */
addCallbacknull124 override fun addCallback(callback: SysUiStateCallback) {
125 stateDispatcher.registerListener(callback)
126 callback.onSystemUiStateChanged(flags, displayId)
127 }
128
129 /** Callback will no longer receive events on state change */
removeCallbacknull130 override fun removeCallback(callback: SysUiStateCallback) {
131 stateDispatcher.unregisterListener(callback)
132 }
133
134 /** Methods to this call can be chained together before calling [.commitUpdate]. */
setFlagnull135 override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
136 if (ShadeWindowGoesAround.isEnabled && bitCount(flag) > 1) {
137 error("Flags should be a single bit.")
138 }
139 val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
140 stateChange.setFlag(flag, toSet)
141 return this
142 }
143
144 @Deprecated(
145 "Each SysUIState instance is now display specific. Just use commitUpdate.",
146 ReplaceWith("commitUpdate()"),
147 )
commitUpdatenull148 override fun commitUpdate(displayId: Int) {
149 commitUpdate()
150 }
151
commitUpdatenull152 override fun commitUpdate() {
153 val newState = stateChange.applyTo(flags)
154 notifyAndSetSystemUiStateChanged(newState, flags)
155 stateChange.clear()
156 }
157
158 /** Notify all those who are registered that the state has changed. */
notifyAndSetSystemUiStateChangednull159 private fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long) {
160 if (SysUiState.DEBUG) {
161 Log.d(TAG, "SysUiState changed for displayId=$displayId: old=$oldFlags new=$newFlags")
162 }
163 if (newFlags != oldFlags) {
164 _flags = newFlags
165 stateDispatcher.dispatchSysUIStateChange(newFlags, displayId)
166 }
167 }
168
169 @NeverCompile
dumpnull170 override fun dump(pw: PrintWriter, args: Array<String>) {
171 pw.println("SysUiState state:")
172 pw.print(" mSysUiStateFlags=")
173 pw.println(flags)
174 pw.println(" " + QuickStepContract.getSystemUiStateString(flags))
175 pw.print(" backGestureDisabled=")
176 pw.println(QuickStepContract.isBackGestureDisabled(flags, false /* forTrackpad */))
177 pw.print(" assistantGestureDisabled=")
178 pw.println(QuickStepContract.isAssistantGestureDisabled(flags))
179 pw.print(" pendingStateChanges=")
180 pw.println(stateChange.toString())
181 }
182
destroynull183 override fun destroy() {
184 dumpManager.unregisterDumpable(debugName)
185 }
186
187 @AssistedFactory
188 interface Factory {
189 /** Creates a new instance of [SysUiStateImpl] for a given [displayId]. */
createnull190 fun create(displayId: Int): SysUiStateImpl
191 }
192
193 companion object {
194 private val TAG: String = SysUiState::class.java.simpleName
195 }
196 }
197
198 /** Returns the flag value taking into account [SceneContainerPlugin] potential overrides. */
flagWithOptionalOverridesnull199 fun flagWithOptionalOverrides(
200 flag: Long,
201 enabled: Boolean,
202 displayId: Int,
203 sceneContainerPlugin: SceneContainerPlugin?,
204 ): Boolean {
205 var toSet = enabled
206 val overrideOrNull = sceneContainerPlugin?.flagValueOverride(flag = flag, displayId = displayId)
207 if (overrideOrNull != null && toSet != overrideOrNull) {
208 if (SysUiState.DEBUG) {
209 Log.d(
210 TAG,
211 "setFlag for flag $flag and value $toSet overridden to " +
212 "$overrideOrNull by scene container plugin",
213 )
214 }
215
216 toSet = overrideOrNull
217 }
218 return toSet
219 }
220
221 /** Creates and destroy instances of [SysUiState] */
222 @SysUISingleton
223 class SysUIStateInstanceProvider
224 @Inject
225 constructor(
226 private val factory: SysUiStateImpl.Factory,
227 private val overrideFactory: SysUIStateOverride.Factory,
228 ) : PerDisplayInstanceProviderWithTeardown<SysUiState> {
createInstancenull229 override fun createInstance(displayId: Int): SysUiState {
230 return if (displayId == Display.DEFAULT_DISPLAY) {
231 factory.create(displayId)
232 } else {
233 overrideFactory.create(displayId)
234 }
235 .apply { start() }
236 }
237
destroyInstancenull238 override fun destroyInstance(instance: SysUiState) {
239 instance.destroy()
240 }
241 }
242