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.content.pm.PackageManager.MATCH_ANY_USER;
20 
21 import android.annotation.Nullable;
22 import android.app.compat.ChangeIdStateCache;
23 import android.app.compat.PackageOverride;
24 import android.compat.Compatibility.ChangeConfig;
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageManager;
28 import android.os.Build;
29 import android.os.Environment;
30 import android.text.TextUtils;
31 import android.util.LongArray;
32 import android.util.Slog;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.compat.AndroidBuildClassifier;
37 import com.android.internal.compat.CompatibilityChangeConfig;
38 import com.android.internal.compat.CompatibilityChangeInfo;
39 import com.android.internal.compat.CompatibilityOverrideConfig;
40 import com.android.internal.compat.CompatibilityOverridesByPackageConfig;
41 import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig;
42 import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
43 import com.android.internal.compat.IOverrideValidator;
44 import com.android.internal.compat.OverrideAllowedState;
45 import com.android.server.compat.config.Change;
46 import com.android.server.compat.config.Config;
47 import com.android.server.compat.overrides.ChangeOverrides;
48 import com.android.server.compat.overrides.Overrides;
49 import com.android.server.compat.overrides.XmlWriter;
50 import com.android.server.pm.ApexManager;
51 
52 import org.xmlpull.v1.XmlPullParserException;
53 
54 import java.io.BufferedInputStream;
55 import java.io.File;
56 import java.io.FileInputStream;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.io.PrintWriter;
60 import java.util.Arrays;
61 import java.util.HashSet;
62 import java.util.List;
63 import java.util.Set;
64 import java.util.concurrent.ConcurrentHashMap;
65 import java.util.concurrent.atomic.AtomicBoolean;
66 
67 import javax.xml.datatype.DatatypeConfigurationException;
68 
69 /**
70  * CompatConfig maintains state related to the platform compatibility changes.
71  *
72  * <p>It stores the default configuration for each change, and any per-package overrides that have
73  * been configured.
74  */
75 final class CompatConfig {
76     private static final String TAG = "CompatConfig";
77     private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
78     private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
79     private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
80 
81     private final ConcurrentHashMap<Long, CompatChange> mChanges = new ConcurrentHashMap<>();
82 
83     private final OverrideValidatorImpl mOverrideValidator;
84     private final AndroidBuildClassifier mAndroidBuildClassifier;
85     private Context mContext;
86     private final Object mOverridesFileLock = new Object();
87     @GuardedBy("mOverridesFileLock")
88     private File mOverridesFile;
89     @GuardedBy("mOverridesFileLock")
90     private File mBackupOverridesFile;
91 
92     @VisibleForTesting
CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context)93     CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
94         mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
95         mAndroidBuildClassifier = androidBuildClassifier;
96         mContext = context;
97     }
98 
create(AndroidBuildClassifier androidBuildClassifier, Context context)99     static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
100         CompatConfig config = new CompatConfig(androidBuildClassifier, context);
101         config.initConfigFromLib(Environment.buildPath(
102                 Environment.getRootDirectory(), "etc", "compatconfig"));
103         config.initConfigFromLib(Environment.buildPath(
104                 Environment.getRootDirectory(), "system_ext", "etc", "compatconfig"));
105 
106         List<ApexManager.ActiveApexInfo> apexes = ApexManager.getInstance().getActiveApexInfos();
107         for (ApexManager.ActiveApexInfo apex : apexes) {
108             config.initConfigFromLib(Environment.buildPath(
109                     apex.apexDirectory, "etc", "compatconfig"));
110         }
111         config.initOverrides();
112         config.invalidateCache();
113         return config;
114     }
115 
116     /**
117      * Adds a change.
118      *
119      * <p>This is intended to be used by unit tests only.
120      *
121      * @param change the change to add
122      */
123     @VisibleForTesting
addChange(CompatChange change)124     void addChange(CompatChange change) {
125         mChanges.put(change.getId(), change);
126     }
127 
128     /**
129      * Retrieves the set of disabled changes for a given app.
130      *
131      * <p>Any change ID not in the returned array is by default enabled for the app.
132      *
133      * <p>We use a primitive array to minimize memory footprint: every app process will store this
134      * array statically so we aim to reduce overhead as much as possible.
135      *
136      * @param app the app in question
137      * @return a sorted long array of change IDs
138      */
getDisabledChanges(ApplicationInfo app)139     long[] getDisabledChanges(ApplicationInfo app) {
140         LongArray disabled = new LongArray();
141         for (CompatChange c : mChanges.values()) {
142             if (!c.isEnabled(app, mAndroidBuildClassifier)) {
143                 disabled.add(c.getId());
144             }
145         }
146         final long[] sortedChanges = disabled.toArray();
147         Arrays.sort(sortedChanges);
148         return sortedChanges;
149     }
150 
151     /**
152      * Retrieves the set of changes that are intended to be logged. This includes changes that
153      * target the most recent SDK version and are not disabled.
154      *
155      * @param app the app in question
156      * @return a sorted long array of change IDs
157      */
getLoggableChanges(ApplicationInfo app)158     long[] getLoggableChanges(ApplicationInfo app) {
159         LongArray loggable = new LongArray(mChanges.size());
160         for (CompatChange c : mChanges.values()) {
161             long changeId = c.getId();
162             boolean isLatestSdk = isChangeTargetingLatestSdk(c, app.targetSdkVersion);
163             if (c.isEnabled(app, mAndroidBuildClassifier) && isLatestSdk) {
164                 loggable.add(changeId);
165             }
166         }
167         final long[] sortedChanges = loggable.toArray();
168         Arrays.sort(sortedChanges);
169         return sortedChanges;
170     }
171 
172     /**
173      * Whether the change indicated by the given changeId is targeting the latest SDK version.
174      * @param c             the change for which to check the target SDK version
175      * @param appSdkVersion the target sdk version of the app
176      * @return true if the changeId targets the current sdk version or the current development
177      * version.
178      */
isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion)179     boolean isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion) {
180         int maxTargetSdk = maxTargetSdkForCompatChange(c) + 1;
181         if (maxTargetSdk <= 0) {
182             // No max target sdk found.
183             return false;
184         }
185 
186         return maxTargetSdk == Build.VERSION_CODES.CUR_DEVELOPMENT || maxTargetSdk == appSdkVersion;
187     }
188 
189     /**
190      * Retrieves the CompatChange associated with the given changeId. Will return null if the
191      * changeId is not found. Used only for performance improvement purposes, in order to reduce
192      * lookups.
193      *
194      * @param changeId for which to look up the CompatChange
195      * @return the found compat change, or null if not found.
196      */
getCompatChange(long changeId)197     CompatChange getCompatChange(long changeId) {
198         return mChanges.get(changeId);
199     }
200 
201     /**
202      * Looks up a change ID by name.
203      *
204      * @param name name of the change to look up
205      * @return the change ID, or {@code -1} if no change with that name exists
206      */
lookupChangeId(String name)207     long lookupChangeId(String name) {
208         for (CompatChange c : mChanges.values()) {
209             if (TextUtils.equals(c.getName(), name)) {
210                 return c.getId();
211             }
212         }
213         return -1;
214     }
215 
216     /**
217      * Checks if a given change id is enabled for a given application.
218      *
219      * @param changeId the ID of the change in question
220      * @param app      app to check for
221      * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
222      * change ID is not known, as unknown changes are enabled by default.
223      */
isChangeEnabled(long changeId, ApplicationInfo app)224     boolean isChangeEnabled(long changeId, ApplicationInfo app) {
225         CompatChange c = mChanges.get(changeId);
226         return isChangeEnabled(c, app);
227     }
228 
229     /**
230      * Checks if a given change is enabled for a given application.
231      *
232      * @param c   the CompatChange in question
233      * @param app the app to check for
234      * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
235      * change ID is not known, as unknown changes are enabled by default.
236      */
isChangeEnabled(CompatChange c, ApplicationInfo app)237     boolean isChangeEnabled(CompatChange c, ApplicationInfo app) {
238         if (c == null) {
239             // we know nothing about this change: default behaviour is enabled.
240             return true;
241         }
242         return c.isEnabled(app, mAndroidBuildClassifier);
243     }
244 
245     /**
246      * Checks if a given change will be enabled for a given package name after the installation.
247      *
248      * @param changeId    the ID of the change in question
249      * @param packageName package name to check for
250      * @return {@code true} if the change would be enabled for this package name. Also returns
251      * {@code true} if the change ID is not known, as unknown changes are enabled by default.
252      */
willChangeBeEnabled(long changeId, String packageName)253     boolean willChangeBeEnabled(long changeId, String packageName) {
254         CompatChange c = mChanges.get(changeId);
255         if (c == null) {
256             // we know nothing about this change: default behaviour is enabled.
257             return true;
258         }
259         return c.willBeEnabled(packageName);
260     }
261 
262     /**
263      * Overrides the enabled state for a given change and app.
264      *
265      * <p>This method is intended to be used *only* for debugging purposes, ultimately invoked
266      * either by an adb command, or from some developer settings UI.
267      *
268      * <p>Note: package overrides are not persistent and will be lost on system or runtime restart.
269      *
270      * @param changeId    the ID of the change to be overridden. Note, this call will succeed even
271      *                    if this change is not known; it will only have any effect if any code in
272      *                    the platform is gated on the ID given.
273      * @param packageName the app package name to override the change for
274      * @param enabled     if the change should be enabled or disabled
275      * @return {@code true} if the change existed before adding the override
276      * @throws IllegalStateException if overriding is not allowed
277      */
addOverride(long changeId, String packageName, boolean enabled)278     synchronized boolean addOverride(long changeId, String packageName, boolean enabled) {
279         boolean alreadyKnown = addOverrideUnsafe(changeId, packageName,
280                 new PackageOverride.Builder().setEnabled(enabled).build());
281         saveOverrides();
282         invalidateCache();
283         return alreadyKnown;
284     }
285 
286     /**
287      * Adds compat config overrides for multiple packages.
288      *
289      * <p>Equivalent to calling
290      * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} on each entry
291      * in {@code overridesByPackage}, but the state of the compat config will be updated only
292      * once instead of for each package.
293      *
294      * @param overridesByPackage map from package name to compat config overrides to add for that
295      *                           package.
296      * @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overridesByPackage}.
297      */
addAllPackageOverrides( CompatibilityOverridesByPackageConfig overridesByPackage, boolean skipUnknownChangeIds)298     synchronized void addAllPackageOverrides(
299             CompatibilityOverridesByPackageConfig overridesByPackage,
300             boolean skipUnknownChangeIds) {
301         for (String packageName : overridesByPackage.packageNameToOverrides.keySet()) {
302             addPackageOverridesWithoutSaving(
303                     overridesByPackage.packageNameToOverrides.get(packageName), packageName,
304                     skipUnknownChangeIds);
305         }
306         saveOverrides();
307         invalidateCache();
308     }
309 
310     /**
311      * Adds compat config overrides for a given package.
312      *
313      * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
314      *
315      * @param overrides   list of compat config overrides to add for the given package.
316      * @param packageName app for which the overrides will be applied.
317      * @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overrides}.
318      */
addPackageOverrides(CompatibilityOverrideConfig overrides, String packageName, boolean skipUnknownChangeIds)319     synchronized void addPackageOverrides(CompatibilityOverrideConfig overrides,
320             String packageName, boolean skipUnknownChangeIds) {
321         addPackageOverridesWithoutSaving(overrides, packageName, skipUnknownChangeIds);
322         saveOverrides();
323         invalidateCache();
324     }
325 
addPackageOverridesWithoutSaving(CompatibilityOverrideConfig overrides, String packageName, boolean skipUnknownChangeIds)326     private void addPackageOverridesWithoutSaving(CompatibilityOverrideConfig overrides,
327             String packageName, boolean skipUnknownChangeIds) {
328         for (Long changeId : overrides.overrides.keySet()) {
329             if (skipUnknownChangeIds && !isKnownChangeId(changeId)) {
330                 Slog.w(TAG, "Trying to add overrides for unknown Change ID " + changeId + ". "
331                         + "Skipping Change ID.");
332                 continue;
333             }
334             addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
335         }
336     }
337 
addOverrideUnsafe(long changeId, String packageName, PackageOverride overrides)338     private boolean addOverrideUnsafe(long changeId, String packageName,
339             PackageOverride overrides) {
340         final AtomicBoolean alreadyKnown = new AtomicBoolean(true);
341         OverrideAllowedState allowedState =
342                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
343         allowedState.enforce(changeId, packageName);
344         Long versionCode = getVersionCodeOrNull(packageName);
345 
346         final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> {
347             alreadyKnown.set(false);
348             return new CompatChange(changeId);
349         });
350         c.addPackageOverride(packageName, overrides, allowedState, versionCode);
351         Slog.d(TAG, (overrides.isEnabled() ? "Enabled" : "Disabled")
352                 + " change " + changeId + (c.getName() != null ? " [" + c.getName() + "]" : "")
353                 + " for " + packageName);
354         invalidateCache();
355         return alreadyKnown.get();
356     }
357 
358     /** Checks whether the change is known to the compat config. */
isKnownChangeId(long changeId)359     boolean isKnownChangeId(long changeId) {
360         return mChanges.containsKey(changeId);
361     }
362 
363     /**
364      * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
365      * target SDK gated).
366      *
367      * @param changeId the id of the CompatChange to check for the max target sdk
368      */
maxTargetSdkForChangeIdOptIn(long changeId)369     int maxTargetSdkForChangeIdOptIn(long changeId) {
370         CompatChange c = mChanges.get(changeId);
371         return maxTargetSdkForCompatChange(c);
372     }
373 
374     /**
375      * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
376      * target SDK gated).
377      *
378      * @param c the CompatChange to check for the max target sdk
379      */
maxTargetSdkForCompatChange(CompatChange c)380     int maxTargetSdkForCompatChange(CompatChange c) {
381         if (c != null && c.getEnableSinceTargetSdk() != -1) {
382             return c.getEnableSinceTargetSdk() - 1;
383         }
384         return -1;
385     }
386 
387     /**
388      * Returns whether the change is marked as logging only.
389      */
isLoggingOnly(long changeId)390     boolean isLoggingOnly(long changeId) {
391         CompatChange c = mChanges.get(changeId);
392         return c != null && c.getLoggingOnly();
393     }
394 
395     /**
396      * Returns whether the change is marked as disabled.
397      */
isDisabled(long changeId)398     boolean isDisabled(long changeId) {
399         CompatChange c = mChanges.get(changeId);
400         return c != null && c.getDisabled();
401     }
402 
403     /**
404      * Returns whether the change is overridable.
405      */
isOverridable(long changeId)406     boolean isOverridable(long changeId) {
407         CompatChange c = mChanges.get(changeId);
408         return c != null && c.getOverridable();
409     }
410 
411     /**
412      * Removes an override previously added via {@link #addOverride(long, String, boolean)}.
413      *
414      * <p>This restores the default behaviour for the given change and app, once any app processes
415      * have been restarted.
416      *
417      * @param changeId    the ID of the change that was overridden
418      * @param packageName the app package name that was overridden
419      * @return {@code true} if an override existed;
420      */
removeOverride(long changeId, String packageName)421     synchronized boolean removeOverride(long changeId, String packageName) {
422         boolean overrideExists = removeOverrideUnsafe(changeId, packageName);
423         if (overrideExists) {
424             saveOverrides();
425             invalidateCache();
426         }
427         return overrideExists;
428     }
429 
430     /**
431      * Unsafe version of {@link #removeOverride(long, String)}.
432      * It does not save the overrides.
433      */
removeOverrideUnsafe(long changeId, String packageName)434     private boolean removeOverrideUnsafe(long changeId, String packageName) {
435         Long versionCode = getVersionCodeOrNull(packageName);
436         CompatChange c = mChanges.get(changeId);
437         if (c != null) {
438             return removeOverrideUnsafe(c, packageName, versionCode);
439         }
440         return false;
441     }
442 
443     /**
444      * Similar to {@link #removeOverrideUnsafe(long, String)} except this method receives a {@link
445      * CompatChange} directly as well as the package's version code.
446      */
removeOverrideUnsafe(CompatChange change, String packageName, @Nullable Long versionCode)447     private boolean removeOverrideUnsafe(CompatChange change, String packageName,
448             @Nullable Long versionCode) {
449         long changeId = change.getId();
450         OverrideAllowedState allowedState =
451                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
452         boolean overrideExists = change.removePackageOverride(packageName, allowedState,
453                 versionCode);
454         if (overrideExists) {
455             Slog.d(TAG, "Reset change " + changeId
456                     + (change.getName() != null ? " [" + change.getName() + "]" : "")
457                     + " for " + packageName + " to default value.");
458         }
459         return overrideExists;
460     }
461 
462     /**
463      * Removes overrides with a specified change ID that were previously added via
464      * {@link #addOverride(long, String, boolean)} or
465      * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for multiple
466      * packages.
467      *
468      * <p>Equivalent to calling
469      * {@link #removePackageOverrides(CompatibilityOverridesToRemoveConfig, String)} on each entry
470      * in {@code overridesToRemoveByPackage}, but the state of the compat config will be updated
471      * only once instead of for each package.
472      *
473      * @param overridesToRemoveByPackage map from package name to a list of change IDs for
474      *                                   which to restore the default behaviour for that
475      *                                   package.
476      */
removeAllPackageOverrides( CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage)477     synchronized void removeAllPackageOverrides(
478             CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) {
479         boolean shouldInvalidateCache = false;
480         for (String packageName :
481                 overridesToRemoveByPackage.packageNameToOverridesToRemove.keySet()) {
482             shouldInvalidateCache |= removePackageOverridesWithoutSaving(
483                     overridesToRemoveByPackage.packageNameToOverridesToRemove.get(packageName),
484                     packageName);
485         }
486         if (shouldInvalidateCache) {
487             saveOverrides();
488             invalidateCache();
489         }
490     }
491 
492     /**
493      * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
494      * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain
495      * package.
496      *
497      * <p>This restores the default behaviour for the given app.
498      *
499      * @param packageName the package for which the overrides should be purged
500      */
removePackageOverrides(String packageName)501     synchronized void removePackageOverrides(String packageName) {
502         Long versionCode = getVersionCodeOrNull(packageName);
503         boolean shouldInvalidateCache = false;
504         for (CompatChange change : mChanges.values()) {
505             shouldInvalidateCache |= removeOverrideUnsafe(change, packageName, versionCode);
506         }
507         if (shouldInvalidateCache) {
508             saveOverrides();
509             invalidateCache();
510         }
511     }
512 
513     /**
514      * Removes overrides whose change ID is specified in {@code overridesToRemove} that were
515      * previously added via {@link #addOverride(long, String, boolean)} or
516      * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain
517      * package.
518      *
519      * <p>This restores the default behaviour for the given change IDs and app.
520      *
521      * @param overridesToRemove list of change IDs for which to restore the default behaviour.
522      * @param packageName       the package for which the overrides should be purged
523      */
removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName)524     synchronized void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove,
525             String packageName) {
526         boolean shouldInvalidateCache = removePackageOverridesWithoutSaving(overridesToRemove,
527                 packageName);
528         if (shouldInvalidateCache) {
529             saveOverrides();
530             invalidateCache();
531         }
532     }
533 
removePackageOverridesWithoutSaving( CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName)534     private boolean removePackageOverridesWithoutSaving(
535             CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) {
536         boolean shouldInvalidateCache = false;
537         for (Long changeId : overridesToRemove.changeIds) {
538             if (!isKnownChangeId(changeId)) {
539                 Slog.w(TAG, "Trying to remove overrides for unknown Change ID " + changeId + ". "
540                         + "Skipping Change ID.");
541                 continue;
542             }
543             shouldInvalidateCache |= removeOverrideUnsafe(changeId, packageName);
544         }
545         return shouldInvalidateCache;
546     }
547 
getAllowedChangesSinceTargetSdkForPackage(String packageName, int targetSdkVersion)548     private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName,
549             int targetSdkVersion) {
550         LongArray allowed = new LongArray();
551         for (CompatChange change : mChanges.values()) {
552             if (change.getEnableSinceTargetSdk() != targetSdkVersion) {
553                 continue;
554             }
555             OverrideAllowedState allowedState =
556                     mOverrideValidator.getOverrideAllowedState(change.getId(),
557                             packageName);
558             if (allowedState.state == OverrideAllowedState.ALLOWED) {
559                 allowed.add(change.getId());
560             }
561         }
562         return allowed.toArray();
563     }
564 
565     /**
566      * Enables all changes with enabledSinceTargetSdk == {@param targetSdkVersion} for
567      * {@param packageName}.
568      *
569      * @return the number of changes that were toggled
570      */
enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion)571     int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
572         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
573         boolean shouldInvalidateCache = false;
574         for (long changeId : changes) {
575             shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName,
576                     new PackageOverride.Builder().setEnabled(true).build());
577         }
578         if (shouldInvalidateCache) {
579             saveOverrides();
580             invalidateCache();
581         }
582         return changes.length;
583     }
584 
585     /**
586      * Disables all changes with enabledSinceTargetSdk == {@param targetSdkVersion} for
587      * {@param packageName}.
588      *
589      * @return the number of changes that were toggled
590      */
disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion)591     int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
592         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
593         boolean shouldInvalidateCache = false;
594         for (long changeId : changes) {
595             shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName,
596                     new PackageOverride.Builder().setEnabled(false).build());
597         }
598         if (shouldInvalidateCache) {
599             saveOverrides();
600             invalidateCache();
601         }
602         return changes.length;
603     }
604 
registerListener(long changeId, CompatChange.ChangeListener listener)605     boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
606         final AtomicBoolean alreadyKnown = new AtomicBoolean(true);
607         final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> {
608             alreadyKnown.set(false);
609             invalidateCache();
610             return new CompatChange(changeId);
611         });
612         c.registerListener(listener);
613         return alreadyKnown.get();
614     }
615 
defaultChangeIdValue(long changeId)616     boolean defaultChangeIdValue(long changeId) {
617         CompatChange c = mChanges.get(changeId);
618         if (c == null) {
619             return true;
620         }
621         return c.defaultValue();
622     }
623 
624     @VisibleForTesting
forceNonDebuggableFinalForTest(boolean value)625     void forceNonDebuggableFinalForTest(boolean value) {
626         mOverrideValidator.forceNonDebuggableFinalForTest(value);
627     }
628 
629     @VisibleForTesting
clearChanges()630     void clearChanges() {
631         mChanges.clear();
632     }
633 
634     /**
635      * Dumps the current list of compatibility config information.
636      *
637      * @param pw {@link PrintWriter} instance to which the information will be dumped
638      */
dumpConfig(PrintWriter pw)639     void dumpConfig(PrintWriter pw) {
640         if (mChanges.size() == 0) {
641             pw.println("No compat overrides.");
642             return;
643         }
644         for (CompatChange c : mChanges.values()) {
645             pw.println(c.toString());
646         }
647     }
648 
649     /**
650      * Returns config for a given app.
651      *
652      * @param applicationInfo the {@link ApplicationInfo} for which the info should be dumped
653      */
getAppConfig(ApplicationInfo applicationInfo)654     CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) {
655         Set<Long> enabled = new HashSet<>();
656         Set<Long> disabled = new HashSet<>();
657         for (CompatChange c : mChanges.values()) {
658             if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) {
659                 enabled.add(c.getId());
660             } else {
661                 disabled.add(c.getId());
662             }
663         }
664         return new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled));
665     }
666 
667     /**
668      * Dumps all the compatibility change information.
669      *
670      * @return an array of {@link CompatibilityChangeInfo} with the current changes
671      */
dumpChanges()672     CompatibilityChangeInfo[] dumpChanges() {
673         CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()];
674         int i = 0;
675         for (CompatChange change : mChanges.values()) {
676             changeInfos[i++] = new CompatibilityChangeInfo(change);
677         }
678         return changeInfos;
679     }
680 
initConfigFromLib(File libraryDir)681     void initConfigFromLib(File libraryDir) {
682         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
683             Slog.d(TAG, "No directory " + libraryDir + ", skipping");
684             return;
685         }
686         for (File f : libraryDir.listFiles()) {
687             Slog.d(TAG, "Found a config file: " + f.getPath());
688             //TODO(b/138222363): Handle duplicate ids across config files.
689             readConfig(f);
690         }
691     }
692 
readConfig(File configFile)693     private void readConfig(File configFile) {
694         try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
695             Config config = com.android.server.compat.config.XmlParser.read(in);
696             for (Change change : config.getCompatChange()) {
697                 Slog.d(TAG, "Adding: " + change.toString());
698                 mChanges.put(change.getId(), new CompatChange(change));
699             }
700         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
701             Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e);
702         } finally {
703             invalidateCache();
704         }
705     }
706 
initOverrides()707     private void initOverrides() {
708         initOverrides(new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE),
709                 new File(STATIC_OVERRIDES_PRODUCT_DIR, OVERRIDES_FILE));
710     }
711 
712     @VisibleForTesting
initOverrides(File dynamicOverridesFile, File staticOverridesFile)713     void initOverrides(File dynamicOverridesFile, File staticOverridesFile) {
714         // Clear overrides from all changes before loading.
715 
716         for (CompatChange c : mChanges.values()) {
717             c.clearOverrides();
718         }
719 
720 
721         loadOverrides(staticOverridesFile);
722 
723         synchronized (mOverridesFileLock) {
724             mOverridesFile = dynamicOverridesFile;
725             mBackupOverridesFile = makeBackupFile(dynamicOverridesFile);
726             if (mBackupOverridesFile.exists()) {
727                 mOverridesFile.delete();
728                 mBackupOverridesFile.renameTo(mOverridesFile);
729             }
730             loadOverrides(mOverridesFile);
731         }
732 
733         if (staticOverridesFile.exists()) {
734             // Only save overrides if there is a static overrides file.
735             saveOverrides();
736         }
737     }
738 
makeBackupFile(File overridesFile)739     private File makeBackupFile(File overridesFile) {
740         return new File(overridesFile.getPath() + ".bak");
741     }
742 
loadOverrides(File overridesFile)743     private void loadOverrides(File overridesFile) {
744         if (!overridesFile.exists()) {
745             // Overrides file doesn't exist.
746             return;
747         }
748 
749         try (InputStream in = new BufferedInputStream(new FileInputStream(overridesFile))) {
750             Overrides overrides = com.android.server.compat.overrides.XmlParser.read(in);
751             if (overrides == null) {
752                 Slog.w(TAG, "Parsing " + overridesFile.getPath() + " failed");
753                 return;
754             }
755             for (ChangeOverrides changeOverrides : overrides.getChangeOverrides()) {
756                 long changeId = changeOverrides.getChangeId();
757                 CompatChange compatChange = mChanges.get(changeId);
758                 if (compatChange == null) {
759                     Slog.w(TAG, "Change ID " + changeId + " not found. "
760                             + "Skipping overrides for it.");
761                     continue;
762                 }
763                 compatChange.loadOverrides(changeOverrides);
764             }
765         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
766             Slog.w(TAG, "Error processing " + overridesFile + " " + e.toString());
767             return;
768         }
769     }
770 
771     /**
772      * Persist compat framework overrides to /data/misc/appcompat/compat_framework_overrides.xml
773      */
saveOverrides()774     void saveOverrides() {
775         synchronized (mOverridesFileLock) {
776             if (mOverridesFile == null || mBackupOverridesFile == null) {
777                 return;
778             }
779 
780             Overrides overrides = new Overrides();
781             List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
782             for (CompatChange c : mChanges.values()) {
783                 ChangeOverrides changeOverrides = c.saveOverrides();
784                 if (changeOverrides != null) {
785                     changeOverridesList.add(changeOverrides);
786                 }
787             }
788 
789             // Rename the file to the backup.
790             if (mOverridesFile.exists()) {
791                 if (mBackupOverridesFile.exists()) {
792                     mOverridesFile.delete();
793                 } else {
794                     if (!mOverridesFile.renameTo(mBackupOverridesFile)) {
795                         Slog.e(TAG, "Couldn't rename file " + mOverridesFile
796                                 + " to " + mBackupOverridesFile);
797                         return;
798                     }
799                 }
800             }
801 
802             // Create the file if it doesn't already exist
803             try {
804                 mOverridesFile.createNewFile();
805             } catch (IOException e) {
806                 Slog.e(TAG, "Could not create override config file: " + e.toString());
807                 return;
808             }
809             try (PrintWriter out = new PrintWriter(mOverridesFile)) {
810                 XmlWriter writer = new XmlWriter(out);
811                 XmlWriter.write(writer, overrides);
812             } catch (IOException e) {
813                 Slog.e(TAG, e.toString());
814             }
815 
816             // Remove the backup if the write succeeds.
817             mBackupOverridesFile.delete();
818         }
819     }
820 
getOverrideValidator()821     IOverrideValidator getOverrideValidator() {
822         return mOverrideValidator;
823     }
824 
invalidateCache()825     private void invalidateCache() {
826         ChangeIdStateCache.invalidate();
827     }
828 
829     /**
830      * Rechecks all the existing overrides for a package.
831      */
recheckOverrides(String packageName)832     void recheckOverrides(String packageName) {
833         Long versionCode = getVersionCodeOrNull(packageName);
834         boolean shouldInvalidateCache = false;
835         for (CompatChange c : mChanges.values()) {
836             OverrideAllowedState allowedState =
837                     mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(),
838                             packageName);
839             shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode);
840         }
841         if (shouldInvalidateCache) {
842             invalidateCache();
843         }
844     }
845 
846     @Nullable
getVersionCodeOrNull(String packageName)847     private Long getVersionCodeOrNull(String packageName) {
848         try {
849             ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
850                     packageName, MATCH_ANY_USER);
851             return applicationInfo.longVersionCode;
852         } catch (PackageManager.NameNotFoundException e) {
853             return null;
854         }
855     }
856 
registerContentObserver()857     void registerContentObserver() {
858         mOverrideValidator.registerContentObserver();
859     }
860 }
861