1 /* 2 * Copyright (C) 2024 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.server.wm.utils; 18 19 import static java.lang.Boolean.FALSE; 20 import static java.lang.Boolean.TRUE; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.UserIdInt; 25 import android.content.pm.PackageManager; 26 import android.util.Slog; 27 28 import java.util.function.BooleanSupplier; 29 30 /** 31 * Utility class which helps with handling with properties to opt-in or 32 * opt-out a specific feature. 33 */ 34 public class OptPropFactory { 35 36 @NonNull 37 private final PackageManager mPackageManager; 38 39 @NonNull 40 private final String mPackageName; 41 42 @UserIdInt 43 private final int mUserId; 44 45 /** 46 * Object responsible to handle optIn and optOut properties. 47 * 48 * @param packageManager The PackageManager reference 49 * @param packageName The name of the package. 50 */ OptPropFactory(@onNull PackageManager packageManager, @NonNull String packageName, @UserIdInt int userId)51 public OptPropFactory(@NonNull PackageManager packageManager, @NonNull String packageName, 52 @UserIdInt int userId) { 53 mPackageManager = packageManager; 54 mPackageName = packageName; 55 mUserId = userId; 56 } 57 58 /** 59 * Creates an OptProp for the given property 60 * 61 * @param propertyName The name of the property. 62 * @return The OptProp for the given property 63 */ 64 @NonNull create(@onNull String propertyName)65 public OptProp create(@NonNull String propertyName) { 66 return OptProp.create( 67 () -> mPackageManager.getPropertyAsUser(propertyName, mPackageName, 68 null /* className */, mUserId).getBoolean(), 69 propertyName); 70 } 71 72 /** 73 * Creates an OptProp for the given property behind a gate condition. 74 * 75 * @param propertyName The name of the property. 76 * @param gateCondition If this resolves to false, the property is unset. This is evaluated at 77 * every interaction with the OptProp. 78 * @return The OptProp for the given property 79 */ 80 @NonNull create(@onNull String propertyName, @NonNull BooleanSupplier gateCondition)81 public OptProp create(@NonNull String propertyName, @NonNull BooleanSupplier gateCondition) { 82 return OptProp.create( 83 () -> mPackageManager.getPropertyAsUser(propertyName, mPackageName, 84 null /* className */, mUserId).getBoolean(), 85 propertyName, 86 gateCondition); 87 } 88 89 @FunctionalInterface 90 private interface ThrowableBooleanSupplier { get()91 boolean get() throws Exception; 92 } 93 94 public static class OptProp { 95 96 private static final int VALUE_UNSET = -2; 97 private static final int VALUE_UNDEFINED = -1; 98 private static final int VALUE_FALSE = 0; 99 private static final int VALUE_TRUE = 1; 100 101 @IntDef(prefix = {"VALUE_"}, value = { 102 VALUE_UNSET, 103 VALUE_UNDEFINED, 104 VALUE_FALSE, 105 VALUE_TRUE, 106 }) 107 @interface OptionalValue {} 108 109 private static final String TAG = "OptProp"; 110 111 // The condition is evaluated every time the OptProp state is accessed. 112 @NonNull 113 private final BooleanSupplier mCondition; 114 115 // This is evaluated only once in the lifetime of an OptProp. 116 @NonNull 117 private final ThrowableBooleanSupplier mValueSupplier; 118 119 @NonNull 120 private final String mPropertyName; 121 122 @OptionalValue 123 private int mValue = VALUE_UNDEFINED; 124 OptProp(@onNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName, @NonNull BooleanSupplier condition)125 private OptProp(@NonNull ThrowableBooleanSupplier valueSupplier, 126 @NonNull String propertyName, 127 @NonNull BooleanSupplier condition) { 128 mValueSupplier = valueSupplier; 129 mPropertyName = propertyName; 130 mCondition = condition; 131 } 132 133 @NonNull create(@onNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName)134 private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier, 135 @NonNull String propertyName) { 136 return new OptProp(valueSupplier, propertyName, () -> true); 137 } 138 139 @NonNull create(@onNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName, @NonNull BooleanSupplier condition)140 private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier, 141 @NonNull String propertyName, @NonNull BooleanSupplier condition) { 142 return new OptProp(valueSupplier, propertyName, condition); 143 } 144 145 /** 146 * @return {@code true} when the guarding condition is {@code true} and the property has 147 * been explicitly set to {@code true}. {@code false} otherwise. The guarding condition is 148 * evaluated every time this method is invoked. 149 */ isTrue()150 public boolean isTrue() { 151 return mCondition.getAsBoolean() && getValue() == VALUE_TRUE; 152 } 153 154 /** 155 * @return {@code true} when the guarding condition is {@code true} and the property has 156 * been explicitly set to {@code false}. {@code false} otherwise. The guarding condition is 157 * evaluated every time this method is invoked. 158 */ isFalse()159 public boolean isFalse() { 160 return mCondition.getAsBoolean() && getValue() == VALUE_FALSE; 161 } 162 163 /** 164 * Returns {@code true} when the following conditions are met: 165 * <ul> 166 * <li>{@code gatingCondition} doesn't evaluate to {@code false} 167 * <li>App developers didn't opt out with a component {@code property} 168 * <li>App developers opted in with a component {@code property} or an OEM opted in with 169 * a per-app override 170 * </ul> 171 * 172 * <p>This is used for the treatments that are enabled only on per-app basis. 173 */ shouldEnableWithOverrideAndProperty(boolean overrideValue)174 public boolean shouldEnableWithOverrideAndProperty(boolean overrideValue) { 175 if (!mCondition.getAsBoolean()) { 176 return false; 177 } 178 if (getValue() == VALUE_FALSE) { 179 return false; 180 } 181 return getValue() == VALUE_TRUE || overrideValue; 182 } 183 184 /** 185 * Returns {@code true} when the following conditions are met: 186 * <ul> 187 * <li>{@code gatingCondition} doesn't evaluate to {@code false} 188 * <li>App developers didn't opt out with a component {@code property} 189 * <li>OEM opted in with a per-app override 190 * </ul> 191 * 192 * <p>This is used for the treatments that are enabled based with the heuristic but can be 193 * disabled on per-app basis by OEMs or app developers. 194 */ shouldEnableWithOptInOverrideAndOptOutProperty( boolean overrideValue)195 public boolean shouldEnableWithOptInOverrideAndOptOutProperty( 196 boolean overrideValue) { 197 if (!mCondition.getAsBoolean()) { 198 return false; 199 } 200 return getValue() != VALUE_FALSE && overrideValue; 201 } 202 203 /** 204 * Returns {@code true} when the following conditions are met: 205 * <ul> 206 * <li>{@code gatingCondition} doesn't resolve to {@code false} 207 * <li>OEM didn't opt out with a per-app override 208 * <li>App developers didn't opt out with a component {@code property} 209 * </ul> 210 * 211 * <p>This is used for the treatments that are enabled based with the heuristic but can be 212 * disabled on per-app basis by OEMs or app developers. 213 */ shouldEnableWithOptOutOverrideAndProperty(boolean overrideValue)214 public boolean shouldEnableWithOptOutOverrideAndProperty(boolean overrideValue) { 215 if (!mCondition.getAsBoolean()) { 216 return false; 217 } 218 return getValue() != VALUE_FALSE && !overrideValue; 219 } 220 221 @OptionalValue getValue()222 private int getValue() { 223 if (mValue == VALUE_UNDEFINED) { 224 try { 225 final Boolean value = mValueSupplier.get(); 226 if (TRUE.equals(value)) { 227 mValue = VALUE_TRUE; 228 } else if (FALSE.equals(value)) { 229 mValue = VALUE_FALSE; 230 } else { 231 mValue = VALUE_UNSET; 232 } 233 } catch (Exception e) { 234 Slog.w(TAG, "Cannot read opt property " + mPropertyName); 235 mValue = VALUE_UNSET; 236 } 237 } 238 return mValue; 239 } 240 } 241 } 242