1 /* 2 * Copyright (C) 2021 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.content.pm; 18 19 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; 20 21 import android.provider.DeviceConfig; 22 import android.util.ArrayMap; 23 import android.util.Pair; 24 import android.util.Slog; 25 26 import com.android.internal.os.BackgroundThread; 27 28 import java.util.Arrays; 29 import java.util.List; 30 import java.util.Map; 31 32 /** 33 * Class for processing flags in the Device Config namespace 'constrain_display_apis'. 34 * 35 * @hide 36 */ 37 public final class ConstrainDisplayApisConfig { 38 private static final String TAG = ConstrainDisplayApisConfig.class.getSimpleName(); 39 40 /** 41 * A string flag whose value holds a comma separated list of package entries in the format 42 * '<package-name>:<min-version-code>?:<max-version-code>?' for which Display APIs should never 43 * be constrained. 44 */ 45 private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS = "never_constrain_display_apis"; 46 47 /** 48 * A boolean flag indicating whether Display APIs should never be constrained for all 49 * packages. If true, {@link #FLAG_NEVER_CONSTRAIN_DISPLAY_APIS} is ignored. 50 */ 51 private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES = 52 "never_constrain_display_apis_all_packages"; 53 54 /** 55 * A string flag whose value holds a comma separated list of package entries in the format 56 * '<package-name>:<min-version-code>?:<max-version-code>?' for which Display APIs should 57 * always be constrained. 58 */ 59 private static final String FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS = 60 "always_constrain_display_apis"; 61 62 /** 63 * Indicates that display APIs should never be constrained to the activity window bounds for all 64 * packages. 65 */ 66 private boolean mNeverConstrainDisplayApisAllPackages; 67 68 /** 69 * Indicates that display APIs should never be constrained to the activity window bounds for 70 * a set of defined packages. Map keys are package names, and entries are a 71 * 'Pair(<min-version-code>, <max-version-code>)'. 72 */ 73 private ArrayMap<String, Pair<Long, Long>> mNeverConstrainConfigMap; 74 75 /** 76 * Indicates that display APIs should always be constrained to the activity window bounds for 77 * a set of defined packages. Map keys are package names, and entries are a 78 * 'Pair(<min-version-code>, <max-version-code>)'. 79 */ 80 private ArrayMap<String, Pair<Long, Long>> mAlwaysConstrainConfigMap; 81 ConstrainDisplayApisConfig()82 public ConstrainDisplayApisConfig() { 83 updateCache(); 84 85 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONSTRAIN_DISPLAY_APIS, 86 BackgroundThread.getExecutor(), properties -> updateCache()); 87 } 88 89 /** 90 * Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the 91 * flag 'never_constrain_display_apis' contains a package entry that matches the given {@code 92 * applicationInfo}. 93 * 94 * @param applicationInfo Information about the application/package. 95 */ getNeverConstrainDisplayApis(ApplicationInfo applicationInfo)96 public boolean getNeverConstrainDisplayApis(ApplicationInfo applicationInfo) { 97 if (mNeverConstrainDisplayApisAllPackages) { 98 return true; 99 } 100 101 return flagHasMatchingPackageEntry(mNeverConstrainConfigMap, applicationInfo); 102 } 103 104 /** 105 * Returns true if the flag 'always_constrain_display_apis' contains a package entry that 106 * matches the given {@code applicationInfo}. 107 * 108 * @param applicationInfo Information about the application/package. 109 */ getAlwaysConstrainDisplayApis(ApplicationInfo applicationInfo)110 public boolean getAlwaysConstrainDisplayApis(ApplicationInfo applicationInfo) { 111 return flagHasMatchingPackageEntry(mAlwaysConstrainConfigMap, applicationInfo); 112 } 113 114 115 /** 116 * Updates {@link #mNeverConstrainDisplayApisAllPackages}, {@link #mNeverConstrainConfigMap}, 117 * and {@link #mAlwaysConstrainConfigMap} from the {@link DeviceConfig}. 118 */ updateCache()119 private void updateCache() { 120 mNeverConstrainDisplayApisAllPackages = DeviceConfig.getBoolean( 121 NAMESPACE_CONSTRAIN_DISPLAY_APIS, 122 FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false); 123 124 final String neverConstrainConfigStr = DeviceConfig.getString( 125 NAMESPACE_CONSTRAIN_DISPLAY_APIS, 126 FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); 127 mNeverConstrainConfigMap = buildConfigMap(neverConstrainConfigStr); 128 129 final String alwaysConstrainConfigStr = DeviceConfig.getString( 130 NAMESPACE_CONSTRAIN_DISPLAY_APIS, 131 FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); 132 mAlwaysConstrainConfigMap = buildConfigMap(alwaysConstrainConfigStr); 133 } 134 135 /** 136 * Processes the configuration string into a map of version codes, for the given 137 * configuration to be applied to the specified packages. If the given package 138 * entry string is invalid, then the map will not contain an entry for the package. 139 * 140 * @param configStr A configuration string expected to be in the format of a list of package 141 * entries separated by ','. A package entry expected to be in the format 142 * '<package-name>:<min-version-code>?:<max-version-code>?'. 143 * @return a map of configuration entries, where each key is a package name. Each value is 144 * a pair of version codes, in the format 'Pair(<min-version-code>, <max-version-code>)'. 145 */ buildConfigMap(String configStr)146 private static ArrayMap<String, Pair<Long, Long>> buildConfigMap(String configStr) { 147 ArrayMap<String, Pair<Long, Long>> configMap = new ArrayMap<>(); 148 // String#split returns a non-empty array given an empty string. 149 if (configStr.isEmpty()) { 150 return configMap; 151 } 152 for (String packageEntryString : configStr.split(",")) { 153 List<String> packageAndVersions = Arrays.asList(packageEntryString.split(":", 3)); 154 if (packageAndVersions.size() != 3) { 155 Slog.w(TAG, "Invalid package entry in flag 'never/always_constrain_display_apis': " 156 + packageEntryString); 157 // Skip this entry. 158 continue; 159 } 160 String packageName = packageAndVersions.get(0); 161 String minVersionCodeStr = packageAndVersions.get(1); 162 String maxVersionCodeStr = packageAndVersions.get(2); 163 try { 164 final long minVersion = 165 minVersionCodeStr.isEmpty() ? Long.MIN_VALUE : Long.parseLong( 166 minVersionCodeStr); 167 final long maxVersion = 168 maxVersionCodeStr.isEmpty() ? Long.MAX_VALUE : Long.parseLong( 169 maxVersionCodeStr); 170 Pair<Long, Long> minMaxVersionCodes = new Pair<>(minVersion, maxVersion); 171 configMap.put(packageName, minMaxVersionCodes); 172 } catch (NumberFormatException e) { 173 Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryString); 174 // Skip this entry. 175 } 176 } 177 return configMap; 178 } 179 180 /** 181 * Returns true if the flag with the given {@code flagName} contains a package entry that 182 * matches the given {@code applicationInfo}. 183 * 184 * @param configMap the map representing the current configuration value to examine 185 * @param applicationInfo Information about the application/package. 186 */ flagHasMatchingPackageEntry(Map<String, Pair<Long, Long>> configMap, ApplicationInfo applicationInfo)187 private static boolean flagHasMatchingPackageEntry(Map<String, Pair<Long, Long>> configMap, 188 ApplicationInfo applicationInfo) { 189 if (configMap.isEmpty()) { 190 return false; 191 } 192 if (!configMap.containsKey(applicationInfo.packageName)) { 193 return false; 194 } 195 return matchesApplicationInfo(configMap.get(applicationInfo.packageName), applicationInfo); 196 } 197 198 /** 199 * Parses the given {@code minMaxVersionCodes} and returns true if {@code 200 * applicationInfo.longVersionCode} is within the version range in the pair. 201 * Returns false otherwise. 202 * 203 * @param minMaxVersionCodes A pair expected to be in the format 204 * 'Pair(<min-version-code>, <max-version-code>)'. 205 * @param applicationInfo Information about the application/package. 206 */ matchesApplicationInfo(Pair<Long, Long> minMaxVersionCodes, ApplicationInfo applicationInfo)207 private static boolean matchesApplicationInfo(Pair<Long, Long> minMaxVersionCodes, 208 ApplicationInfo applicationInfo) { 209 return applicationInfo.longVersionCode >= minMaxVersionCodes.first 210 && applicationInfo.longVersionCode <= minMaxVersionCodes.second; 211 } 212 } 213