1 /* 2 * Copyright (C) 2022 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.shared.condition; 18 19 import android.util.Log; 20 21 import androidx.annotation.NonNull; 22 import androidx.lifecycle.Lifecycle; 23 import androidx.lifecycle.LifecycleEventObserver; 24 import androidx.lifecycle.LifecycleOwner; 25 26 import java.lang.ref.WeakReference; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Collection; 30 import java.util.Iterator; 31 import java.util.List; 32 33 /** 34 * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform 35 * its callbacks. 36 */ 37 public abstract class Condition { 38 private final String mTag = getClass().getSimpleName(); 39 40 private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); 41 private final boolean mOverriding; 42 private Boolean mIsConditionMet; 43 private boolean mStarted = false; 44 45 /** 46 * By default, conditions have an initial value of false and are not overriding. 47 */ Condition()48 public Condition() { 49 this(false, false); 50 } 51 52 /** 53 * Constructor for specifying initial state and overriding condition attribute. 54 * @param initialConditionMet Initial state of the condition. 55 * @param overriding Whether this condition overrides others. 56 */ Condition(Boolean initialConditionMet, boolean overriding)57 protected Condition(Boolean initialConditionMet, boolean overriding) { 58 mIsConditionMet = initialConditionMet; 59 mOverriding = overriding; 60 } 61 62 /** 63 * Starts monitoring the condition. 64 */ start()65 protected abstract void start(); 66 67 /** 68 * Stops monitoring the condition. 69 */ stop()70 protected abstract void stop(); 71 72 /** 73 * Returns whether the current condition overrides 74 */ isOverridingCondition()75 public boolean isOverridingCondition() { 76 return mOverriding; 77 } 78 79 /** 80 * Registers a callback to receive updates once started. This should be called before 81 * {@link #start()}. Also triggers the callback immediately if already started. 82 */ addCallback(@onNull Callback callback)83 public void addCallback(@NonNull Callback callback) { 84 if (shouldLog()) Log.d(mTag, "adding callback"); 85 mCallbacks.add(new WeakReference<>(callback)); 86 87 if (mStarted) { 88 callback.onConditionChanged(this); 89 return; 90 } 91 92 start(); 93 mStarted = true; 94 } 95 96 /** 97 * Removes the provided callback from further receiving updates. 98 */ removeCallback(@onNull Callback callback)99 public void removeCallback(@NonNull Callback callback) { 100 if (shouldLog()) Log.d(mTag, "removing callback"); 101 final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); 102 while (iterator.hasNext()) { 103 final Callback cb = iterator.next().get(); 104 if (cb == null || cb == callback) { 105 iterator.remove(); 106 } 107 } 108 109 if (!mCallbacks.isEmpty() || !mStarted) { 110 return; 111 } 112 113 stop(); 114 mStarted = false; 115 } 116 117 /** 118 * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state 119 * and {@link #removeCallback(Callback)} when not resumed automatically. 120 */ observe(LifecycleOwner owner, Callback listener)121 public Callback observe(LifecycleOwner owner, Callback listener) { 122 return observe(owner.getLifecycle(), listener); 123 } 124 125 /** 126 * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state 127 * and {@link #removeCallback(Condition.Callback)} when not resumed automatically. 128 */ observe(Lifecycle lifecycle, Callback listener)129 public Callback observe(Lifecycle lifecycle, Callback listener) { 130 lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> { 131 if (event == Lifecycle.Event.ON_RESUME) { 132 addCallback(listener); 133 } else if (event == Lifecycle.Event.ON_PAUSE) { 134 removeCallback(listener); 135 } 136 }); 137 return listener; 138 } 139 140 /** 141 * Updates the value for whether the condition has been fulfilled, and sends an update if the 142 * value changes and any callback is registered. 143 * 144 * @param isConditionMet True if the condition has been fulfilled. False otherwise. 145 */ updateCondition(boolean isConditionMet)146 protected void updateCondition(boolean isConditionMet) { 147 if (mIsConditionMet != null && mIsConditionMet == isConditionMet) { 148 return; 149 } 150 151 if (shouldLog()) Log.d(mTag, "updating condition to " + isConditionMet); 152 mIsConditionMet = isConditionMet; 153 sendUpdate(); 154 } 155 156 /** 157 * Clears the set condition value. This is purposefully separate from 158 * {@link #updateCondition(boolean)} to avoid confusion around {@code null} values. 159 */ clearCondition()160 protected void clearCondition() { 161 if (mIsConditionMet == null) { 162 return; 163 } 164 165 if (shouldLog()) Log.d(mTag, "clearing condition"); 166 167 mIsConditionMet = null; 168 sendUpdate(); 169 } 170 sendUpdate()171 private void sendUpdate() { 172 final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); 173 while (iterator.hasNext()) { 174 final Callback cb = iterator.next().get(); 175 if (cb == null) { 176 iterator.remove(); 177 } else { 178 cb.onConditionChanged(this); 179 } 180 } 181 } 182 183 /** 184 * Returns whether the condition is set. This method should be consulted to understand the 185 * value of {@link #isConditionMet()}. 186 * @return {@code true} if value is present, {@code false} otherwise. 187 */ isConditionSet()188 public boolean isConditionSet() { 189 return mIsConditionMet != null; 190 } 191 192 /** 193 * Returns whether the condition has been met. Note that this method will return {@code false} 194 * if the condition is not set as well. 195 */ isConditionMet()196 public boolean isConditionMet() { 197 return Boolean.TRUE.equals(mIsConditionMet); 198 } 199 shouldLog()200 protected final boolean shouldLog() { 201 return Log.isLoggable(mTag, Log.DEBUG); 202 } 203 getTag()204 protected final String getTag() { 205 return mTag; 206 } 207 208 /** 209 * Creates a new condition which will only be true when both this condition and all the provided 210 * conditions are true. 211 */ and(@onNull Collection<Condition> others)212 public Condition and(@NonNull Collection<Condition> others) { 213 final List<Condition> conditions = new ArrayList<>(others); 214 conditions.add(this); 215 return new CombinedCondition(conditions, Evaluator.OP_AND); 216 } 217 218 /** 219 * Creates a new condition which will only be true when both this condition and the provided 220 * condition is true. 221 */ and(@onNull Condition other)222 public Condition and(@NonNull Condition other) { 223 return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND); 224 } 225 226 /** 227 * Creates a new condition which will only be true when either this condition or any of the 228 * provided conditions are true. 229 */ or(@onNull Collection<Condition> others)230 public Condition or(@NonNull Collection<Condition> others) { 231 final List<Condition> conditions = new ArrayList<>(others); 232 conditions.add(this); 233 return new CombinedCondition(conditions, Evaluator.OP_OR); 234 } 235 236 /** 237 * Creates a new condition which will only be true when either this condition or the provided 238 * condition is true. 239 */ or(@onNull Condition other)240 public Condition or(@NonNull Condition other) { 241 return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR); 242 } 243 244 /** 245 * Callback that receives updates about whether the condition has been fulfilled. 246 */ 247 public interface Callback { 248 /** 249 * Called when the fulfillment of the condition changes. 250 * 251 * @param condition The condition in question. 252 */ onConditionChanged(Condition condition)253 void onConditionChanged(Condition condition); 254 } 255 } 256