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