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