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