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             if (compareSdk != app.targetSdkVersion) {
256                 compareSdk = app.targetSdkVersion;
257             }
258             return compareSdk >= getEnableSinceTargetSdk();
259         }
260         return true;
261     }
262 
263     /**
264      * Find if this change will be enabled for the given package after installation.
265      *
266      * @param packageName The package name in question
267      * @return {@code true} if the change should be enabled for the package.
268      */
willBeEnabled(String packageName)269     boolean willBeEnabled(String packageName) {
270         if (packageName == null) {
271             return defaultValue();
272         }
273         final PackageOverride override = mRawOverrides.get(packageName);
274         if (override != null) {
275             switch (override.evaluateForAllVersions()) {
276                 case VALUE_ENABLED:
277                     return true;
278                 case VALUE_DISABLED:
279                     return false;
280                 case VALUE_UNDEFINED:
281                     return defaultValue();
282             }
283         }
284         return defaultValue();
285     }
286 
287     /**
288      * Returns the default value for the change id, assuming there are no overrides.
289      *
290      * @return {@code false} if it's a default disabled change, {@code true} otherwise.
291      */
defaultValue()292     boolean defaultValue() {
293         return !getDisabled();
294     }
295 
clearOverrides()296     synchronized void clearOverrides() {
297         mRawOverrides.clear();
298         mEvaluatedOverrides.clear();
299     }
300 
loadOverrides(ChangeOverrides changeOverrides)301     synchronized void loadOverrides(ChangeOverrides changeOverrides) {
302         // Load deferred overrides for backwards compatibility
303         if (changeOverrides.getDeferred() != null) {
304             for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
305                 mRawOverrides.put(override.getPackageName(),
306                         new PackageOverride.Builder().setEnabled(
307                                 override.getEnabled()).build());
308             }
309         }
310 
311         // Load validated overrides. For backwards compatibility, we also add them to raw overrides.
312         if (changeOverrides.getValidated() != null) {
313             for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
314                 mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled());
315                 mRawOverrides.put(override.getPackageName(),
316                         new PackageOverride.Builder().setEnabled(
317                                 override.getEnabled()).build());
318             }
319         }
320 
321         // Load raw overrides
322         if (changeOverrides.getRaw() != null) {
323             for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) {
324                 PackageOverride packageOverride = new PackageOverride.Builder()
325                         .setMinVersionCode(override.getMinVersionCode())
326                         .setMaxVersionCode(override.getMaxVersionCode())
327                         .setEnabled(override.getEnabled())
328                         .build();
329                 mRawOverrides.put(override.getPackageName(), packageOverride);
330             }
331         }
332     }
333 
saveOverrides()334     synchronized ChangeOverrides saveOverrides() {
335         if (mRawOverrides.isEmpty()) {
336             return null;
337         }
338         ChangeOverrides changeOverrides = new ChangeOverrides();
339         changeOverrides.setChangeId(getId());
340         ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw();
341         List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue();
342         for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) {
343             RawOverrideValue override = new RawOverrideValue();
344             override.setPackageName(entry.getKey());
345             override.setMinVersionCode(entry.getValue().getMinVersionCode());
346             override.setMaxVersionCode(entry.getValue().getMaxVersionCode());
347             override.setEnabled(entry.getValue().isEnabled());
348             rawList.add(override);
349         }
350         changeOverrides.setRaw(rawOverrides);
351 
352         ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
353         List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
354         for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) {
355             OverrideValue override = new OverrideValue();
356             override.setPackageName(entry.getKey());
357             override.setEnabled(entry.getValue());
358             validatedList.add(override);
359         }
360         changeOverrides.setValidated(validatedOverrides);
361         return changeOverrides;
362     }
363 
364     @Override
toString()365     public String toString() {
366         StringBuilder sb = new StringBuilder("ChangeId(")
367                 .append(getId());
368         if (getName() != null) {
369             sb.append("; name=").append(getName());
370         }
371         if (getEnableSinceTargetSdk() != -1) {
372             sb.append("; enableSinceTargetSdk=").append(getEnableSinceTargetSdk());
373         }
374         if (getDisabled()) {
375             sb.append("; disabled");
376         }
377         if (getLoggingOnly()) {
378             sb.append("; loggingOnly");
379         }
380         if (!mEvaluatedOverrides.isEmpty()) {
381             sb.append("; packageOverrides=").append(mEvaluatedOverrides);
382         }
383         if (!mRawOverrides.isEmpty()) {
384             sb.append("; rawOverrides=").append(mRawOverrides);
385         }
386         if (getOverridable()) {
387             sb.append("; overridable");
388         }
389         return sb.append(")").toString();
390     }
391 
notifyListener(String packageName)392     private synchronized void notifyListener(String packageName) {
393         if (mListener != null) {
394             mListener.onCompatChange(packageName);
395         }
396     }
397 }
398