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