• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.shared.condition
17 
18 import android.util.Log
19 import androidx.annotation.IntDef
20 import androidx.lifecycle.Lifecycle
21 import androidx.lifecycle.LifecycleEventObserver
22 import androidx.lifecycle.LifecycleOwner
23 import java.lang.ref.WeakReference
24 import kotlinx.coroutines.CoroutineScope
25 import kotlinx.coroutines.Job
26 import kotlinx.coroutines.launch
27 
28 /**
29  * Base class for a condition that needs to be fulfilled in order for [Monitor] to inform its
30  * callbacks.
31  */
32 abstract class Condition
33 /**
34  * Constructor for specifying initial state and overriding condition attribute.
35  *
36  * @param initialConditionMet Initial state of the condition.
37  * @param overriding Whether this condition overrides others.
38  */
39 @JvmOverloads
40 protected constructor(
41     private val _scope: CoroutineScope,
42     private var _isConditionMet: Boolean? = false,
43     /** Returns whether the current condition overrides */
44     val isOverridingCondition: Boolean = false,
45 ) {
46     private val mTag: String = javaClass.simpleName
47 
48     private val callbacks = mutableListOf<WeakReference<Callback>>()
49     private var started = false
50     private var currentJob: Job? = null
51 
52     /** Starts monitoring the condition. */
53     protected abstract suspend fun start()
54 
55     /** Stops monitoring the condition. */
56     protected abstract fun stop()
57 
58     @Retention(AnnotationRetention.SOURCE)
59     @IntDef(START_EAGERLY, START_LAZILY, START_WHEN_NEEDED)
60     annotation class StartStrategy
61 
62     @get:StartStrategy abstract val startStrategy: Int
63 
64     /**
65      * Registers a callback to receive updates once started. This should be called before [.start].
66      * Also triggers the callback immediately if already started.
67      */
68     fun addCallback(callback: Callback) {
69         if (shouldLog()) Log.d(mTag, "adding callback")
70         callbacks.add(WeakReference(callback))
71 
72         if (started) {
73             callback.onConditionChanged(this)
74             return
75         }
76 
77         currentJob = _scope.launch { start() }
78         started = true
79     }
80 
81     /** Removes the provided callback from further receiving updates. */
82     fun removeCallback(callback: Callback) {
83         if (shouldLog()) Log.d(mTag, "removing callback")
84         val iterator = callbacks.iterator()
85         while (iterator.hasNext()) {
86             val cb = iterator.next().get()
87             if (cb == null || cb === callback) {
88                 iterator.remove()
89             }
90         }
91 
92         if (callbacks.isNotEmpty() || !started) {
93             return
94         }
95 
96         stop()
97         currentJob?.cancel()
98         currentJob = null
99 
100         started = false
101     }
102 
103     /**
104      * Wrapper to [.addCallback] when a lifecycle is in the resumed state and [.removeCallback] when
105      * not resumed automatically.
106      */
107     fun observe(owner: LifecycleOwner, listener: Callback): Callback {
108         return observe(owner.lifecycle, listener)
109     }
110 
111     /**
112      * Wrapper to [.addCallback] when a lifecycle is in the resumed state and [.removeCallback] when
113      * not resumed automatically.
114      */
115     fun observe(lifecycle: Lifecycle, listener: Callback): Callback {
116         lifecycle.addObserver(
117             LifecycleEventObserver { lifecycleOwner: LifecycleOwner?, event: Lifecycle.Event ->
118                 if (event == Lifecycle.Event.ON_RESUME) {
119                     addCallback(listener)
120                 } else if (event == Lifecycle.Event.ON_PAUSE) {
121                     removeCallback(listener)
122                 }
123             }
124         )
125         return listener
126     }
127 
128     /**
129      * Updates the value for whether the condition has been fulfilled, and sends an update if the
130      * value changes and any callback is registered.
131      *
132      * @param isConditionMet True if the condition has been fulfilled. False otherwise.
133      */
134     protected fun updateCondition(isConditionMet: Boolean) {
135         if (_isConditionMet != null && _isConditionMet == isConditionMet) {
136             return
137         }
138 
139         if (shouldLog()) Log.d(mTag, "updating condition to $isConditionMet")
140         _isConditionMet = isConditionMet
141         sendUpdate()
142     }
143 
144     /**
145      * Clears the set condition value. This is purposefully separate from [.updateCondition] to
146      * avoid confusion around `null` values.
147      */
148     fun clearCondition() {
149         if (_isConditionMet == null) {
150             return
151         }
152 
153         if (shouldLog()) Log.d(mTag, "clearing condition")
154 
155         _isConditionMet = null
156         sendUpdate()
157     }
158 
159     private fun sendUpdate() {
160         val iterator = callbacks.iterator()
161         while (iterator.hasNext()) {
162             val cb = iterator.next().get()
163             if (cb == null) {
164                 iterator.remove()
165             } else {
166                 cb.onConditionChanged(this)
167             }
168         }
169     }
170 
171     val isConditionSet: Boolean
172         /**
173          * Returns whether the condition is set. This method should be consulted to understand the
174          * value of [.isConditionMet].
175          *
176          * @return `true` if value is present, `false` otherwise.
177          */
178         get() = _isConditionMet != null
179 
180     val isConditionMet: Boolean
181         /**
182          * Returns whether the condition has been met. Note that this method will return `false` if
183          * the condition is not set as well.
184          */
185         get() = true == _isConditionMet
186 
187     protected fun shouldLog(): Boolean {
188         return Log.isLoggable(mTag, Log.DEBUG)
189     }
190 
191     val tag: String
192         get() {
193             if (isOverridingCondition) {
194                 return "$mTag[OVRD]"
195             }
196 
197             return mTag
198         }
199 
200     val state: String
201         /**
202          * Returns the state of the condition.
203          * - "Invalid", condition hasn't been set / not monitored
204          * - "True", condition has been met
205          * - "False", condition has not been met
206          */
207         get() {
208             if (!isConditionSet) {
209                 return "Invalid"
210             }
211             return if (isConditionMet) "True" else "False"
212         }
213 
214     /**
215      * Creates a new condition which will only be true when both this condition and all the provided
216      * conditions are true.
217      */
218     fun and(others: Collection<Condition>): Condition {
219         val conditions: List<Condition> = listOf(this, *others.toTypedArray())
220         return CombinedCondition(_scope, conditions, Evaluator.OP_AND)
221     }
222 
223     /**
224      * Creates a new condition which will only be true when both this condition and the provided
225      * condition is true.
226      */
227     fun and(vararg others: Condition): Condition {
228         return and(listOf(*others))
229     }
230 
231     /**
232      * Creates a new condition which will only be true when either this condition or any of the
233      * provided conditions are true.
234      */
235     fun or(others: Collection<Condition>): Condition {
236         val conditions: MutableList<Condition> = ArrayList()
237         conditions.add(this)
238         conditions.addAll(others)
239         return CombinedCondition(_scope, conditions, Evaluator.OP_OR)
240     }
241 
242     /**
243      * Creates a new condition which will only be true when either this condition or the provided
244      * condition is true.
245      */
246     fun or(vararg others: Condition): Condition {
247         return or(listOf(*others))
248     }
249 
250     /** Callback that receives updates about whether the condition has been fulfilled. */
251     fun interface Callback {
252         /**
253          * Called when the fulfillment of the condition changes.
254          *
255          * @param condition The condition in question.
256          */
257         fun onConditionChanged(condition: Condition)
258     }
259 
260     companion object {
261         /** Condition should be started as soon as there is an active subscription. */
262         const val START_EAGERLY: Int = 0
263 
264         /**
265          * Condition should be started lazily only if needed. But once started, it will not be
266          * cancelled unless there are no more active subscriptions.
267          */
268         const val START_LAZILY: Int = 1
269 
270         /**
271          * Condition should be started lazily only if needed, and can be stopped when not needed.
272          * This should be used for conditions which are expensive to keep running.
273          */
274         const val START_WHEN_NEEDED: Int = 2
275     }
276 }
277