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.Manifest.permission.LOG_COMPAT_CHANGE; 20 import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG; 21 import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD; 22 import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; 23 24 import android.annotation.EnforcePermission; 25 import android.annotation.RequiresNoPermission; 26 import android.annotation.UserIdInt; 27 import android.app.ActivityManager; 28 import android.app.IActivityManager; 29 import android.app.compat.PackageOverride; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageManagerInternal; 36 import android.net.Uri; 37 import android.os.Binder; 38 import android.os.Build; 39 import android.os.Process; 40 import android.os.RemoteException; 41 import android.os.UserHandle; 42 import android.util.Slog; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.compat.AndroidBuildClassifier; 46 import com.android.internal.compat.ChangeReporter; 47 import com.android.internal.compat.CompatibilityChangeConfig; 48 import com.android.internal.compat.CompatibilityChangeInfo; 49 import com.android.internal.compat.CompatibilityOverrideConfig; 50 import com.android.internal.compat.CompatibilityOverridesByPackageConfig; 51 import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig; 52 import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; 53 import com.android.internal.compat.IOverrideValidator; 54 import com.android.internal.compat.IPlatformCompat; 55 import com.android.internal.util.DumpUtils; 56 import com.android.server.LocalServices; 57 58 import java.io.FileDescriptor; 59 import java.io.PrintWriter; 60 import java.util.Arrays; 61 import java.util.Collection; 62 import java.util.HashMap; 63 import java.util.Map; 64 65 /** 66 * System server internal API for gating and reporting compatibility changes. 67 */ 68 public class PlatformCompat extends IPlatformCompat.Stub { 69 70 private static final String TAG = "Compatibility"; 71 72 private final Context mContext; 73 private final ChangeReporter mChangeReporter; 74 private final CompatConfig mCompatConfig; 75 private final AndroidBuildClassifier mBuildClassifier; 76 PlatformCompat(Context context)77 public PlatformCompat(Context context) { 78 mContext = context; 79 mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); 80 mBuildClassifier = new AndroidBuildClassifier(); 81 mCompatConfig = CompatConfig.create(mBuildClassifier, mContext); 82 } 83 84 @VisibleForTesting PlatformCompat(Context context, CompatConfig compatConfig, AndroidBuildClassifier buildClassifier)85 PlatformCompat(Context context, CompatConfig compatConfig, 86 AndroidBuildClassifier buildClassifier) { 87 mContext = context; 88 mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); 89 mCompatConfig = compatConfig; 90 mBuildClassifier = buildClassifier; 91 92 registerPackageReceiver(context); 93 } 94 95 @Override 96 @EnforcePermission(LOG_COMPAT_CHANGE) reportChange(long changeId, ApplicationInfo appInfo)97 public void reportChange(long changeId, ApplicationInfo appInfo) { 98 super.reportChange_enforcePermission(); 99 100 reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); 101 } 102 103 @Override 104 @EnforcePermission(LOG_COMPAT_CHANGE) reportChangeByPackageName(long changeId, String packageName, @UserIdInt int userId)105 public void reportChangeByPackageName(long changeId, String packageName, 106 @UserIdInt int userId) { 107 super.reportChangeByPackageName_enforcePermission(); 108 109 ApplicationInfo appInfo = getApplicationInfo(packageName, userId); 110 if (appInfo != null) { 111 reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); 112 } 113 } 114 115 @Override 116 @EnforcePermission(LOG_COMPAT_CHANGE) reportChangeByUid(long changeId, int uid)117 public void reportChangeByUid(long changeId, int uid) { 118 super.reportChangeByUid_enforcePermission(); 119 120 reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED); 121 } 122 reportChangeInternal(long changeId, int uid, int state)123 private void reportChangeInternal(long changeId, int uid, int state) { 124 mChangeReporter.reportChange(uid, changeId, state); 125 } 126 127 @Override 128 @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) isChangeEnabled(long changeId, ApplicationInfo appInfo)129 public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { 130 super.isChangeEnabled_enforcePermission(); 131 132 return isChangeEnabledInternal(changeId, appInfo); 133 } 134 135 @Override 136 @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) isChangeEnabledByPackageName(long changeId, String packageName, @UserIdInt int userId)137 public boolean isChangeEnabledByPackageName(long changeId, String packageName, 138 @UserIdInt int userId) { 139 super.isChangeEnabledByPackageName_enforcePermission(); 140 141 ApplicationInfo appInfo = getApplicationInfo(packageName, userId); 142 if (appInfo == null) { 143 return mCompatConfig.willChangeBeEnabled(changeId, packageName); 144 } 145 return isChangeEnabledInternal(changeId, appInfo); 146 } 147 148 @Override 149 @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) isChangeEnabledByUid(long changeId, int uid)150 public boolean isChangeEnabledByUid(long changeId, int uid) { 151 super.isChangeEnabledByUid_enforcePermission(); 152 153 String[] packages = mContext.getPackageManager().getPackagesForUid(uid); 154 if (packages == null || packages.length == 0) { 155 return mCompatConfig.defaultChangeIdValue(changeId); 156 } 157 boolean enabled = true; 158 for (String packageName : packages) { 159 enabled &= isChangeEnabledByPackageName(changeId, packageName, 160 UserHandle.getUserId(uid)); 161 } 162 return enabled; 163 } 164 165 /** 166 * Internal version of the above method, without logging. 167 * 168 * <p>Does not perform costly permission check. 169 * TODO(b/167551701): Remove this method and add 'loggability' as a changeid property. 170 */ isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo)171 public boolean isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo) { 172 return mCompatConfig.isChangeEnabled(changeId, appInfo); 173 } 174 175 /** 176 * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. 177 * 178 * <p>Does not perform costly permission check. 179 */ isChangeEnabledInternal(long changeId, ApplicationInfo appInfo)180 public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) { 181 boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo); 182 if (appInfo != null) { 183 reportChangeInternal(changeId, appInfo.uid, 184 enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED); 185 } 186 return enabled; 187 } 188 189 /** 190 * Called by the package manager to check if a given change is enabled for a given package name 191 * and the target sdk version while the package is in the parsing state. 192 * 193 * <p>Does not perform costly permission check. 194 * 195 * @param changeId the ID of the change in question 196 * @param packageName package name to check for 197 * @param targetSdkVersion target sdk version to check for 198 * @return {@code true} if the change would be enabled for this package name. 199 */ isChangeEnabledInternal(long changeId, String packageName, int targetSdkVersion)200 public boolean isChangeEnabledInternal(long changeId, String packageName, 201 int targetSdkVersion) { 202 if (mCompatConfig.willChangeBeEnabled(changeId, packageName)) { 203 final ApplicationInfo appInfo = new ApplicationInfo(); 204 appInfo.packageName = packageName; 205 appInfo.targetSdkVersion = targetSdkVersion; 206 return isChangeEnabledInternalNoLogging(changeId, appInfo); 207 } 208 return false; 209 } 210 211 @Override 212 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) setOverrides(CompatibilityChangeConfig overrides, String packageName)213 public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { 214 super.setOverrides_enforcePermission(); 215 216 Map<Long, PackageOverride> overridesMap = new HashMap<>(); 217 for (long change : overrides.enabledChanges()) { 218 overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); 219 } 220 for (long change : overrides.disabledChanges()) { 221 overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) 222 .build()); 223 } 224 mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap), 225 packageName, /* skipUnknownChangeIds */ false); 226 killPackage(packageName); 227 } 228 229 @Override 230 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)231 public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { 232 super.setOverridesForTest_enforcePermission(); 233 234 Map<Long, PackageOverride> overridesMap = new HashMap<>(); 235 for (long change : overrides.enabledChanges()) { 236 overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); 237 } 238 for (long change : overrides.disabledChanges()) { 239 overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) 240 .build()); 241 } 242 mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap), 243 packageName, /* skipUnknownChangeIds */ false); 244 } 245 246 @Override 247 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) putAllOverridesOnReleaseBuilds( CompatibilityOverridesByPackageConfig overridesByPackage)248 public void putAllOverridesOnReleaseBuilds( 249 CompatibilityOverridesByPackageConfig overridesByPackage) { 250 super.putAllOverridesOnReleaseBuilds_enforcePermission(); 251 252 for (CompatibilityOverrideConfig overrides : 253 overridesByPackage.packageNameToOverrides.values()) { 254 checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); 255 } 256 mCompatConfig.addAllPackageOverrides(overridesByPackage, /* skipUnknownChangeIds= */ true); 257 } 258 259 @Override 260 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, String packageName)261 public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, 262 String packageName) { 263 super.putOverridesOnReleaseBuilds_enforcePermission(); 264 265 checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); 266 mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true); 267 } 268 269 @Override 270 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) enableTargetSdkChanges(String packageName, int targetSdkVersion)271 public int enableTargetSdkChanges(String packageName, int targetSdkVersion) { 272 super.enableTargetSdkChanges_enforcePermission(); 273 274 int numChanges = 275 mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion); 276 killPackage(packageName); 277 return numChanges; 278 } 279 280 @Override 281 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) disableTargetSdkChanges(String packageName, int targetSdkVersion)282 public int disableTargetSdkChanges(String packageName, int targetSdkVersion) { 283 super.disableTargetSdkChanges_enforcePermission(); 284 285 int numChanges = 286 mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion); 287 killPackage(packageName); 288 return numChanges; 289 } 290 291 @Override 292 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) clearOverrides(String packageName)293 public void clearOverrides(String packageName) { 294 super.clearOverrides_enforcePermission(); 295 296 mCompatConfig.removePackageOverrides(packageName); 297 killPackage(packageName); 298 } 299 300 @Override 301 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) clearOverridesForTest(String packageName)302 public void clearOverridesForTest(String packageName) { 303 super.clearOverridesForTest_enforcePermission(); 304 305 mCompatConfig.removePackageOverrides(packageName); 306 } 307 308 @Override 309 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) clearOverride(long changeId, String packageName)310 public boolean clearOverride(long changeId, String packageName) { 311 super.clearOverride_enforcePermission(); 312 313 boolean existed = mCompatConfig.removeOverride(changeId, packageName); 314 killPackage(packageName); 315 return existed; 316 } 317 318 @Override 319 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) clearOverrideForTest(long changeId, String packageName)320 public boolean clearOverrideForTest(long changeId, String packageName) { 321 super.clearOverrideForTest_enforcePermission(); 322 323 return mCompatConfig.removeOverride(changeId, packageName); 324 } 325 326 @Override 327 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) removeAllOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage)328 public void removeAllOverridesOnReleaseBuilds( 329 CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) { 330 super.removeAllOverridesOnReleaseBuilds_enforcePermission(); 331 332 for (CompatibilityOverridesToRemoveConfig overridesToRemove : 333 overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) { 334 checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); 335 } 336 mCompatConfig.removeAllPackageOverrides(overridesToRemoveByPackage); 337 } 338 339 @Override 340 @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) removeOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName)341 public void removeOverridesOnReleaseBuilds( 342 CompatibilityOverridesToRemoveConfig overridesToRemove, 343 String packageName) { 344 super.removeOverridesOnReleaseBuilds_enforcePermission(); 345 346 checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); 347 mCompatConfig.removePackageOverrides(overridesToRemove, packageName); 348 } 349 350 @Override 351 @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) getAppConfig(ApplicationInfo appInfo)352 public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { 353 super.getAppConfig_enforcePermission(); 354 355 return mCompatConfig.getAppConfig(appInfo); 356 } 357 358 @Override 359 @EnforcePermission(READ_COMPAT_CHANGE_CONFIG) listAllChanges()360 public CompatibilityChangeInfo[] listAllChanges() { 361 super.listAllChanges_enforcePermission(); 362 363 return mCompatConfig.dumpChanges(); 364 } 365 366 @Override 367 @RequiresNoPermission listUIChanges()368 public CompatibilityChangeInfo[] listUIChanges() { 369 return Arrays.stream(listAllChanges()).filter(this::isShownInUI).toArray( 370 CompatibilityChangeInfo[]::new); 371 } 372 373 /** Checks whether the change is known to the compat config. */ isKnownChangeId(long changeId)374 public boolean isKnownChangeId(long changeId) { 375 return mCompatConfig.isKnownChangeId(changeId); 376 } 377 378 /** 379 * Retrieves the set of disabled changes for a given app. Any change ID not in the returned 380 * array is by default enabled for the app. 381 * 382 * @param appInfo The app in question 383 * @return A sorted long array of change IDs. We use a primitive array to minimize memory 384 * footprint: Every app process will store this array statically so we aim to reduce 385 * overhead as much as possible. 386 */ getDisabledChanges(ApplicationInfo appInfo)387 public long[] getDisabledChanges(ApplicationInfo appInfo) { 388 return mCompatConfig.getDisabledChanges(appInfo); 389 } 390 391 /** 392 * Look up a change ID by name. 393 * 394 * @param name Name of the change to look up 395 * @return The change ID, or {@code -1} if no change with that name exists. 396 */ lookupChangeId(String name)397 public long lookupChangeId(String name) { 398 return mCompatConfig.lookupChangeId(name); 399 } 400 401 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)402 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 403 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) { 404 return; 405 } 406 mContext.enforceCallingOrSelfPermission( 407 READ_COMPAT_CHANGE_CONFIG, "Cannot read compat change"); 408 mContext.enforceCallingOrSelfPermission( 409 LOG_COMPAT_CHANGE, "Cannot read log compat change usage"); 410 mCompatConfig.dumpConfig(pw); 411 } 412 413 @Override 414 @RequiresNoPermission getOverrideValidator()415 public IOverrideValidator getOverrideValidator() { 416 return mCompatConfig.getOverrideValidator(); 417 } 418 419 /** 420 * Clears information stored about events reported on behalf of an app. 421 * 422 * <p>To be called once upon app start or end. A second call would be a no-op. 423 * 424 * @param appInfo the app to reset 425 */ resetReporting(ApplicationInfo appInfo)426 public void resetReporting(ApplicationInfo appInfo) { 427 mChangeReporter.resetReportedChanges(appInfo.uid); 428 } 429 getApplicationInfo(String packageName, int userId)430 private ApplicationInfo getApplicationInfo(String packageName, int userId) { 431 return LocalServices.getService(PackageManagerInternal.class).getApplicationInfo( 432 packageName, 0, Process.myUid(), userId); 433 } 434 killPackage(String packageName)435 private void killPackage(String packageName) { 436 int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName, 437 0, UserHandle.myUserId()); 438 439 if (uid < 0) { 440 Slog.w(TAG, "Didn't find package " + packageName + " on device."); 441 return; 442 } 443 444 Slog.d(TAG, "Killing package " + packageName + " (UID " + uid + ")."); 445 killUid(UserHandle.getAppId(uid)); 446 } 447 killUid(int appId)448 private void killUid(int appId) { 449 final long identity = Binder.clearCallingIdentity(); 450 try { 451 IActivityManager am = ActivityManager.getService(); 452 if (am != null) { 453 am.killUid(appId, UserHandle.USER_ALL, "PlatformCompat overrides"); 454 } 455 } catch (RemoteException e) { 456 /* ignore - same process */ 457 } finally { 458 Binder.restoreCallingIdentity(identity); 459 } 460 } 461 checkAllCompatOverridesAreOverridable(Collection<Long> changeIds)462 private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) { 463 for (Long changeId : changeIds) { 464 if (isKnownChangeId(changeId) && !mCompatConfig.isOverridable(changeId)) { 465 throw new SecurityException("Only change ids marked as Overridable can be " 466 + "overridden."); 467 } 468 } 469 } 470 isShownInUI(CompatibilityChangeInfo change)471 private boolean isShownInUI(CompatibilityChangeInfo change) { 472 if (change.getLoggingOnly()) { 473 return false; 474 } 475 if (change.getId() == CompatChange.CTS_SYSTEM_API_CHANGEID) { 476 return false; 477 } 478 if (change.getEnableSinceTargetSdk() > 0) { 479 return change.getEnableSinceTargetSdk() >= Build.VERSION_CODES.Q 480 && change.getEnableSinceTargetSdk() <= mBuildClassifier.platformTargetSdk(); 481 } 482 return true; 483 } 484 485 /** 486 * Registers a listener for change state overrides. 487 * 488 * <p>Only one listener per change is allowed. 489 * 490 * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with 491 * packageName before the app is killed upon an override change. The state of a change is not 492 * guaranteed to change when {@code listener.onCompatChange(String)} is called. 493 * 494 * @param changeId to get updates for 495 * @param listener the listener that will be called upon a potential change for package 496 * @return {@code true} if a change with changeId was already known, or (@code false} 497 * otherwise 498 * @throws IllegalStateException if a listener was already registered for changeId 499 */ registerListener(long changeId, CompatChange.ChangeListener listener)500 public boolean registerListener(long changeId, CompatChange.ChangeListener listener) { 501 return mCompatConfig.registerListener(changeId, listener); 502 } 503 504 /** 505 * Registers a broadcast receiver that listens for package install, replace or remove. 506 * 507 * @param context the context where the receiver should be registered 508 */ registerPackageReceiver(Context context)509 public void registerPackageReceiver(Context context) { 510 final BroadcastReceiver receiver = new BroadcastReceiver() { 511 @Override 512 public void onReceive(Context context, Intent intent) { 513 if (intent == null) { 514 return; 515 } 516 final Uri packageData = intent.getData(); 517 if (packageData == null) { 518 return; 519 } 520 final String packageName = packageData.getSchemeSpecificPart(); 521 if (packageName == null) { 522 return; 523 } 524 mCompatConfig.recheckOverrides(packageName); 525 } 526 }; 527 IntentFilter filter = new IntentFilter(); 528 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 529 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 530 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 531 filter.addDataScheme("package"); 532 context.registerReceiverForAllUsers(receiver, filter, /* broadcastPermission= */ 533 null, /* scheduler= */ null); 534 } 535 536 /** 537 * Registers the observer for 538 * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT}. 539 */ registerContentObserver()540 public void registerContentObserver() { 541 mCompatConfig.registerContentObserver(); 542 } 543 } 544