1 /* 2 * Copyright (C) 2020 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 android.app.compat; 18 19 import android.annotation.NonNull; 20 import android.annotation.RequiresPermission; 21 import android.annotation.SystemApi; 22 import android.compat.Compatibility; 23 import android.os.RemoteException; 24 import android.os.UserHandle; 25 import android.util.ArrayMap; 26 27 import com.android.internal.compat.CompatibilityOverrideConfig; 28 import com.android.internal.compat.CompatibilityOverridesByPackageConfig; 29 import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig; 30 import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; 31 32 import java.util.Map; 33 import java.util.Set; 34 35 /** 36 * CompatChanges APIs - to be used by platform code only (including mainline 37 * modules). 38 * 39 * @hide 40 */ 41 @SystemApi 42 public final class CompatChanges { 43 private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache(); 44 CompatChanges()45 private CompatChanges() {} 46 47 /** 48 * Query if a given compatibility change is enabled for the current process. This method is 49 * intended to be called by code running inside a process of the affected app only. 50 * 51 * <p>If this method returns {@code true}, the calling code should implement the compatibility 52 * change, resulting in differing behaviour compared to earlier releases. If this method returns 53 * {@code false}, the calling code should behave as it did in earlier releases. 54 * 55 * @param changeId The ID of the compatibility change in question. 56 * @return {@code true} if the change is enabled for the current app. 57 */ isChangeEnabled(long changeId)58 public static boolean isChangeEnabled(long changeId) { 59 return Compatibility.isChangeEnabled(changeId); 60 } 61 62 /** 63 * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an 64 * app from a different process that's performing work for the app. 65 * 66 * <p> Note that this involves a binder call to the system server (unless running in the system 67 * server). If the binder call fails, a {@code RuntimeException} will be thrown. 68 * 69 * @param changeId The ID of the compatibility change in question. 70 * @param packageName The package name of the app in question. 71 * @param user The user that the operation is done for. 72 * @return {@code true} if the change is enabled for the current app. 73 */ 74 @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, 75 android.Manifest.permission.LOG_COMPAT_CHANGE}) isChangeEnabled(long changeId, @NonNull String packageName, @NonNull UserHandle user)76 public static boolean isChangeEnabled(long changeId, @NonNull String packageName, 77 @NonNull UserHandle user) { 78 return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName, 79 user.getIdentifier())); 80 } 81 82 /** 83 * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an 84 * app from a different process that's performing work for the app. 85 * 86 * <p> Note that this involves a binder call to the system server (unless running in the system 87 * server). If the binder call fails, {@code RuntimeException} will be thrown. 88 * 89 * <p> Returns {@code true} if there are no installed packages for the required UID, or if the 90 * change is enabled for ALL of the installed packages associated with the provided UID. Please 91 * use a more specific API if you want a different behaviour for multi-package UIDs. 92 * 93 * @param changeId The ID of the compatibility change in question. 94 * @param uid The UID of the app in question. 95 * @return {@code true} if the change is enabled for the current app. 96 */ 97 @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, 98 android.Manifest.permission.LOG_COMPAT_CHANGE}) isChangeEnabled(long changeId, int uid)99 public static boolean isChangeEnabled(long changeId, int uid) { 100 return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid)); 101 } 102 103 /** 104 * Equivalent to calling {@link #putPackageOverrides(String, Map)} on each entry in {@code 105 * packageNameToOverrides}, but the state of the compat config will be updated only once 106 * instead of for each package. 107 * 108 * @param packageNameToOverrides A map from package name to a map from change ID to the 109 * override applied for that package name and change ID. 110 */ 111 @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) putAllPackageOverrides( @onNull Map<String, Map<Long, PackageOverride>> packageNameToOverrides)112 public static void putAllPackageOverrides( 113 @NonNull Map<String, Map<Long, PackageOverride>> packageNameToOverrides) { 114 ArrayMap<String, CompatibilityOverrideConfig> packageNameToConfig = new ArrayMap<>(); 115 for (String packageName : packageNameToOverrides.keySet()) { 116 packageNameToConfig.put(packageName, 117 new CompatibilityOverrideConfig(packageNameToOverrides.get(packageName))); 118 } 119 CompatibilityOverridesByPackageConfig config = new CompatibilityOverridesByPackageConfig( 120 packageNameToConfig); 121 try { 122 QUERY_CACHE.getPlatformCompatService().putAllOverridesOnReleaseBuilds(config); 123 } catch (RemoteException e) { 124 e.rethrowFromSystemServer(); 125 } 126 } 127 128 /** 129 * Associates app compat overrides with the given package and their respective change IDs. 130 * This will check whether the caller is allowed to perform this operation on the given apk and 131 * build. Only the installer package is allowed to set overrides on a non-debuggable final 132 * build and a non-test apk. 133 * 134 * <p>Note that calling this method doesn't remove previously added overrides for the given 135 * package if their change ID isn't in the given map, only replaces those that have the same 136 * change ID. 137 * 138 * @param packageName The package name of the app in question. 139 * @param overrides A map from change ID to the override applied for this change ID. 140 */ 141 @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) putPackageOverrides(@onNull String packageName, @NonNull Map<Long, PackageOverride> overrides)142 public static void putPackageOverrides(@NonNull String packageName, 143 @NonNull Map<Long, PackageOverride> overrides) { 144 CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides); 145 try { 146 QUERY_CACHE.getPlatformCompatService() 147 .putOverridesOnReleaseBuilds(config, packageName); 148 } catch (RemoteException e) { 149 e.rethrowFromSystemServer(); 150 } 151 } 152 153 /** 154 * Equivalent to calling {@link #removePackageOverrides(String, Set)} on each entry in {@code 155 * packageNameToOverridesToRemove}, but the state of the compat config will be updated only once 156 * instead of for each package. 157 * 158 * @param packageNameToOverridesToRemove A map from package name to a set of change IDs for 159 * which to remove overrides for that package name. 160 */ 161 @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) removeAllPackageOverrides( @onNull Map<String, Set<Long>> packageNameToOverridesToRemove)162 public static void removeAllPackageOverrides( 163 @NonNull Map<String, Set<Long>> packageNameToOverridesToRemove) { 164 ArrayMap<String, CompatibilityOverridesToRemoveConfig> packageNameToConfig = 165 new ArrayMap<>(); 166 for (String packageName : packageNameToOverridesToRemove.keySet()) { 167 packageNameToConfig.put(packageName, 168 new CompatibilityOverridesToRemoveConfig( 169 packageNameToOverridesToRemove.get(packageName))); 170 } 171 CompatibilityOverridesToRemoveByPackageConfig config = 172 new CompatibilityOverridesToRemoveByPackageConfig(packageNameToConfig); 173 try { 174 QUERY_CACHE.getPlatformCompatService().removeAllOverridesOnReleaseBuilds(config); 175 } catch (RemoteException e) { 176 e.rethrowFromSystemServer(); 177 } 178 } 179 180 /** 181 * Removes app compat overrides for the given package. This will check whether the caller is 182 * allowed to perform this operation on the given apk and build. Only the installer package is 183 * allowed to clear overrides on a non-debuggable final build and a non-test apk. 184 * 185 * <p>Note that calling this method with an empty set is a no-op and no overrides will be 186 * removed for the given package. 187 * 188 * @param packageName The package name of the app in question. 189 * @param overridesToRemove A set of change IDs for which to remove overrides. 190 */ 191 @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) removePackageOverrides(@onNull String packageName, @NonNull Set<Long> overridesToRemove)192 public static void removePackageOverrides(@NonNull String packageName, 193 @NonNull Set<Long> overridesToRemove) { 194 CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig( 195 overridesToRemove); 196 try { 197 QUERY_CACHE.getPlatformCompatService() 198 .removeOverridesOnReleaseBuilds(config, packageName); 199 } catch (RemoteException e) { 200 e.rethrowFromSystemServer(); 201 } 202 } 203 } 204