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