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