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