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.annotation.IntDef 20 21 /** 22 * Helper for evaluating a collection of [Condition] objects with a given 23 * [Evaluator.ConditionOperand] 24 */ 25 object Evaluator { 26 /** Operands for combining multiple conditions together */ 27 @Retention(AnnotationRetention.SOURCE) 28 @IntDef(value = [OP_AND, OP_OR]) 29 annotation class ConditionOperand 30 31 /** 32 * 3-valued logical AND operand, with handling for unknown values (represented as null) 33 * 34 * ``` 35 * +-----+----+---+---+ 36 * | AND | T | F | U | 37 * +-----+----+---+---+ 38 * | T | T | F | U | 39 * | F | F | F | F | 40 * | U | U | F | U | 41 * +-----+----+---+---+ 42 * ``` 43 */ 44 const val OP_AND = 0 45 46 /** 47 * 3-valued logical OR operand, with handling for unknown values (represented as null) 48 * 49 * ``` 50 * +-----+----+---+---+ 51 * | OR | T | F | U | 52 * +-----+----+---+---+ 53 * | T | T | T | T | 54 * | F | T | F | U | 55 * | U | T | U | U | 56 * +-----+----+---+---+ 57 * ``` 58 */ 59 const val OP_OR = 1 60 61 /** 62 * Evaluates a set of conditions with a given operand 63 * 64 * If overriding conditions are present, they take precedence over normal conditions if set. 65 * 66 * @param conditions The collection of conditions to evaluate. If empty, null is returned. 67 * @param operand The operand to use when evaluating. 68 * @return Either true or false if the value is known, or null if value is unknown 69 */ 70 fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? { 71 if (conditions.isEmpty()) return null 72 // If there are overriding conditions with values set, they take precedence. 73 val values: Collection<Boolean?> = 74 conditions 75 .filter { it.isConditionSet && it.isOverridingCondition } 76 .ifEmpty { conditions } 77 .map { condition -> 78 if (condition.isConditionSet) { 79 condition.isConditionMet 80 } else { 81 null 82 } 83 } 84 return evaluate(values = values, operand = operand) 85 } 86 87 /** 88 * Evaluates a set of booleans with a given operand 89 * 90 * @param operand The operand to use when evaluating. 91 * @return Either true or false if the value is known, or null if value is unknown 92 */ 93 internal fun evaluate(values: Collection<Boolean?>, @ConditionOperand operand: Int): Boolean? { 94 if (values.isEmpty()) return null 95 return when (operand) { 96 OP_AND -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = false) 97 OP_OR -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = true) 98 else -> null 99 } 100 } 101 102 /** 103 * Helper for evaluating 3-valued logical AND/OR. 104 * 105 * @param returnValueIfAnyMatches AND returns false if any value is false. OR returns true if 106 * any value is true. 107 */ 108 private fun threeValuedAndOrOr( 109 values: Collection<Boolean?>, 110 returnValueIfAnyMatches: Boolean 111 ): Boolean? { 112 var hasUnknown = false 113 for (value in values) { 114 if (value == null) { 115 hasUnknown = true 116 continue 117 } 118 if (value == returnValueIfAnyMatches) { 119 return returnValueIfAnyMatches 120 } 121 } 122 return if (hasUnknown) null else !returnValueIfAnyMatches 123 } 124 } 125