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