1 /*
2  * Copyright (C) 2019 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.compat;
18 
19 import static android.app.compat.PackageOverride.VALUE_DISABLED;
20 import static android.app.compat.PackageOverride.VALUE_ENABLED;
21 import static android.app.compat.PackageOverride.VALUE_UNDEFINED;
22 
23 import android.annotation.Nullable;
24 import android.app.compat.PackageOverride;
25 import android.compat.annotation.ChangeId;
26 import android.compat.annotation.Disabled;
27 import android.compat.annotation.EnabledSince;
28 import android.compat.annotation.Overridable;
29 import android.content.pm.ApplicationInfo;
30 
31 import com.android.internal.compat.AndroidBuildClassifier;
32 import com.android.internal.compat.CompatibilityChangeInfo;
33 import com.android.internal.compat.OverrideAllowedState;
34 import com.android.server.compat.config.Change;
35 import com.android.server.compat.overrides.ChangeOverrides;
36 import com.android.server.compat.overrides.OverrideValue;
37 import com.android.server.compat.overrides.RawOverrideValue;
38 
39 import java.util.List;
40 import java.util.Map;
41 import java.util.concurrent.ConcurrentHashMap;
42 
43 /**
44  * Represents the state of a single compatibility change.
45  *
46  * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk}
47  * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any
48  * target SDK criteria set. These settings can be overridden for a specific package using
49  * {@link #addPackageOverrideInternal(String, boolean)}.
50  *
51  * <p>Note, this class is not thread safe so callers must ensure thread safety.
52  */
53 public final class CompatChange extends CompatibilityChangeInfo {
54 
55     /**
56      * A change ID to be used only in the CTS test for this SystemApi
57      */
58     @ChangeId
59     @EnabledSince(targetSdkVersion = 31) // Needs to be > test APK targetSdkVersion.
60     static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id.
61 
62     /**
63      * An overridable change ID to be used only in the CTS test for this SystemApi
64      */
65     @ChangeId
66     @Disabled
67     @Overridable
68     static final long CTS_SYSTEM_API_OVERRIDABLE_CHANGEID = 174043039; // This is a bug id.
69 
70 
71     /**
72      * Callback listener for when compat changes are updated for a package.
73      * See {@link #registerListener(ChangeListener)} for more details.
74      */
75     public interface ChangeListener {
76         /**
77          * Called upon an override change for packageName and the change this listener is
78          * registered for. Called before the app is killed.
79          */
onCompatChange(String packageName)80         void onCompatChange(String packageName);
81     }
82 
83     ChangeListener mListener = null;
84 
85     private ConcurrentHashMap<String, Boolean> mEvaluatedOverrides;
86     private ConcurrentHashMap<String, PackageOverride> mRawOverrides;
87 
CompatChange(long changeId)88     public CompatChange(long changeId) {
89         this(changeId, null, -1, -1, false, false, null, false);
90     }
91 
92     /**
93      * @param change an object generated by services/core/xsd/platform-compat-config.xsd
94      */
CompatChange(Change change)95     public CompatChange(Change change) {
96         this(change.getId(), change.getName(), change.getEnableAfterTargetSdk(),
97                 change.getEnableSinceTargetSdk(), change.getDisabled(), change.getLoggingOnly(),
98                 change.getDescription(), change.getOverridable());
99     }
100 
101     /**
102      * @param changeId Unique ID for the change. See {@link android.compat.Compatibility}.
103      * @param name Short descriptive name.
104      * @param enableAfterTargetSdk {@code targetSdkVersion} restriction. See {@link EnabledAfter};
105      *                             -1 if the change is always enabled.
106      * @param enableSinceTargetSdk {@code targetSdkVersion} restriction. See {@link EnabledSince};
107      *                             -1 if the change is always enabled.
108      * @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set.
109      */
CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk, int enableSinceTargetSdk, boolean disabled, boolean loggingOnly, String description, boolean overridable)110     public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk,
111             int enableSinceTargetSdk, boolean disabled, boolean loggingOnly, String description,
112             boolean overridable) {
113         super(changeId, name, enableAfterTargetSdk, enableSinceTargetSdk, disabled, loggingOnly,
114               description, overridable);
115 
116         // Initialize override maps.
117         mEvaluatedOverrides = new ConcurrentHashMap<>();
118         mRawOverrides = new ConcurrentHashMap<>();
119     }
120 
registerListener(ChangeListener listener)121     synchronized void registerListener(ChangeListener listener) {
122         if (mListener != null) {
123             throw new IllegalStateException(
124                     "Listener for change " + toString() + " already registered.");
125         }
126         mListener = listener;
127     }
128 
129 
130     /**
131      * Force the enabled state of this change for a given package name. The change will only take
132      * effect after that packages process is killed and restarted.
133      *
134      * @param pname Package name to enable the change for.
135      * @param enabled Whether or not to enable the change.
136      */
addPackageOverrideInternal(String pname, boolean enabled)137     private void addPackageOverrideInternal(String pname, boolean enabled) {
138         if (getLoggingOnly()) {
139             throw new IllegalArgumentException(
140                     "Can't add overrides for a logging only change " + toString());
141         }
142         mEvaluatedOverrides.put(pname, enabled);
143         notifyListener(pname);
144     }
145 
removePackageOverrideInternal(String pname)146     private void removePackageOverrideInternal(String pname) {
147         if (mEvaluatedOverrides.remove(pname) != null) {
148             notifyListener(pname);
149         }
150     }
151 
152     /**
153      * Tentatively set the state of this change for a given package name.
154      * The override will only take effect after that package is installed, if applicable.
155      *
156      * @param packageName Package name to tentatively enable the change for.
157      * @param override The package override to be set
158      * @param allowedState Whether the override is allowed.
159      * @param versionCode The version code of the package.
160      */
addPackageOverride(String packageName, PackageOverride override, OverrideAllowedState allowedState, @Nullable Long versionCode)161     synchronized void addPackageOverride(String packageName, PackageOverride override,
162             OverrideAllowedState allowedState, @Nullable Long versionCode) {
163         if (getLoggingOnly()) {
164             throw new IllegalArgumentException(
165                     "Can't add overrides for a logging only change " + toString());
166         }
167         mRawOverrides.put(packageName, override);
168         recheckOverride(packageName, allowedState, versionCode);
169     }
170 
171     /**
172      * Rechecks an existing (and possibly deferred) override.
173      *
174      * <p>For deferred overrides, check if they can be promoted to a regular override. For regular
175      * overrides, check if they need to be demoted to deferred.</p>
176      *
177      * @param packageName Package name to apply deferred overrides for.
178      * @param allowedState Whether the override is allowed.
179      * @param versionCode The version code of the package.
180      *
181      * @return {@code true} if the recheck yielded a result that requires invalidating caches
182      *         (a deferred override was consolidated or a regular override was removed).
183      */
recheckOverride(String packageName, OverrideAllowedState allowedState, @Nullable Long versionCode)184     synchronized boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
185             @Nullable Long versionCode) {
186         if (packageName == null) {
187             return false;
188         }
189         boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
190         // If the app is not installed or no longer has raw overrides, evaluate to false
191         if (versionCode == null || !mRawOverrides.containsKey(packageName) || !allowed) {
192             removePackageOverrideInternal(packageName);
193             return false;
194         }
195         // Evaluate the override based on its version
196         int overrideValue = mRawOverrides.get(packageName).evaluate(versionCode);
197         switch (overrideValue) {
198             case VALUE_UNDEFINED:
199                 removePackageOverrideInternal(packageName);
200                 break;
201             case VALUE_ENABLED:
202                 addPackageOverrideInternal(packageName, true);
203                 break;
204             case VALUE_DISABLED:
205                 addPackageOverrideInternal(packageName, false);
206                 break;
207         }
208         return true;
209     }
210 
211     /**
212      * Remove any package override for the given package name, restoring the default behaviour.
213      *
214      * <p>Note, this method is not thread safe so callers must ensure thread safety.
215      *
216      * @param pname Package name to reset to defaults for.
217      * @param allowedState Whether the override is allowed.
218      * @param versionCode The version code of the package.
219      */
removePackageOverride(String pname, OverrideAllowedState allowedState, @Nullable Long versionCode)220     synchronized boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
221             @Nullable Long versionCode) {
222         if (mRawOverrides.containsKey(pname)) {
223             allowedState.enforce(getId(), pname);
224             mRawOverrides.remove(pname);
225             recheckOverride(pname, allowedState, versionCode);
226             return true;
227         }
228         return false;
229     }
230 
231     /**
232      * Find if this change is enabled for the given package, taking into account any overrides that
233      * exist.
234      *
235      * @param app Info about the app in question
236      * @return {@code true} if the change should be enabled for the package.
237      */
isEnabled(ApplicationInfo app, AndroidBuildClassifier buildClassifier)238     boolean isEnabled(ApplicationInfo app, AndroidBuildClassifier buildClassifier) {
239         if (app == null) {
240             return defaultValue();
241         }
242         if (app.packageName != null) {
243             final Boolean enabled = mEvaluatedOverrides.get(app.packageName);
244             if (enabled != null) {
245                 return enabled;
246             }
247         }
248         if (getDisabled()) {
249             return false;
250         }
251         if (getEnableSinceTargetSdk() != -1) {
252             // If the change is gated by a platform version newer than the one currently installed
253             // on the device, disregard the app's target sdk version.
254             int compareSdk = Math.min(app.targetSdkVersion, buildClassifier.platformTargetSdk());
255             return compareSdk >= getEnableSinceTargetSdk();
256         }
257         return true;
258     }
259 
260     /**
261      * Find if this change will be enabled for the given package after installation.
262      *
263      * @param packageName The package name in question
264      * @return {@code true} if the change should be enabled for the package.
265      */
willBeEnabled(String packageName)266     boolean willBeEnabled(String packageName) {
267         if (packageName == null) {
268             return defaultValue();
269         }
270         final PackageOverride override = mRawOverrides.get(packageName);
271         if (override != null) {
272             switch (override.evaluateForAllVersions()) {
273                 case VALUE_ENABLED:
274                     return true;
275                 case VALUE_DISABLED:
276                     return false;
277                 case VALUE_UNDEFINED:
278                     return defaultValue();
279             }
280         }
281         return defaultValue();
282     }
283 
284     /**
285      * Returns the default value for the change id, assuming there are no overrides.
286      *
287      * @return {@code false} if it's a default disabled change, {@code true} otherwise.
288      */
defaultValue()289     boolean defaultValue() {
290         return !getDisabled();
291     }
292 
clearOverrides()293     synchronized void clearOverrides() {
294         mRawOverrides.clear();
295         mEvaluatedOverrides.clear();
296     }
297 
loadOverrides(ChangeOverrides changeOverrides)298     synchronized void loadOverrides(ChangeOverrides changeOverrides) {
299         // Load deferred overrides for backwards compatibility
300         if (changeOverrides.getDeferred() != null) {
301             for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
302                 mRawOverrides.put(override.getPackageName(),
303                         new PackageOverride.Builder().setEnabled(
304                                 override.getEnabled()).build());
305             }
306         }
307 
308         // Load validated overrides. For backwards compatibility, we also add them to raw overrides.
309         if (changeOverrides.getValidated() != null) {
310             for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
311                 mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled());
312                 mRawOverrides.put(override.getPackageName(),
313                         new PackageOverride.Builder().setEnabled(
314                                 override.getEnabled()).build());
315             }
316         }
317 
318         // Load raw overrides
319         if (changeOverrides.getRaw() != null) {
320             for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) {
321                 PackageOverride packageOverride = new PackageOverride.Builder()
322                         .setMinVersionCode(override.getMinVersionCode())
323                         .setMaxVersionCode(override.getMaxVersionCode())
324                         .setEnabled(override.getEnabled())
325                         .build();
326                 mRawOverrides.put(override.getPackageName(), packageOverride);
327             }
328         }
329     }
330 
saveOverrides()331     synchronized ChangeOverrides saveOverrides() {
332         if (mRawOverrides.isEmpty()) {
333             return null;
334         }
335         ChangeOverrides changeOverrides = new ChangeOverrides();
336         changeOverrides.setChangeId(getId());
337         ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw();
338         List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue();
339         for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) {
340             RawOverrideValue override = new RawOverrideValue();
341             override.setPackageName(entry.getKey());
342             override.setMinVersionCode(entry.getValue().getMinVersionCode());
343             override.setMaxVersionCode(entry.getValue().getMaxVersionCode());
344             override.setEnabled(entry.getValue().isEnabled());
345             rawList.add(override);
346         }
347         changeOverrides.setRaw(rawOverrides);
348 
349         ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
350         List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
351         for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) {
352             OverrideValue override = new OverrideValue();
353             override.setPackageName(entry.getKey());
354             override.setEnabled(entry.getValue());
355             validatedList.add(override);
356         }
357         changeOverrides.setValidated(validatedOverrides);
358         return changeOverrides;
359     }
360 
361     @Override
toString()362     public String toString() {
363         StringBuilder sb = new StringBuilder("ChangeId(")
364                 .append(getId());
365         if (getName() != null) {
366             sb.append("; name=").append(getName());
367         }
368         if (getEnableSinceTargetSdk() != -1) {
369             sb.append("; enableSinceTargetSdk=").append(getEnableSinceTargetSdk());
370         }
371         if (getDisabled()) {
372             sb.append("; disabled");
373         }
374         if (getLoggingOnly()) {
375             sb.append("; loggingOnly");
376         }
377         if (!mEvaluatedOverrides.isEmpty()) {
378             sb.append("; packageOverrides=").append(mEvaluatedOverrides);
379         }
380         if (!mRawOverrides.isEmpty()) {
381             sb.append("; rawOverrides=").append(mRawOverrides);
382         }
383         if (getOverridable()) {
384             sb.append("; overridable");
385         }
386         return sb.append(")").toString();
387     }
388 
notifyListener(String packageName)389     private synchronized void notifyListener(String packageName) {
390         if (mListener != null) {
391             mListener.onCompatChange(packageName);
392         }
393     }
394 }
395