/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.Pair; import android.util.Slog; import com.android.internal.os.BackgroundThread; import java.util.Arrays; import java.util.List; import java.util.Map; /** * Class for processing flags in the Device Config namespace 'constrain_display_apis'. * * @hide */ public final class ConstrainDisplayApisConfig { private static final String TAG = ConstrainDisplayApisConfig.class.getSimpleName(); /** * A string flag whose value holds a comma separated list of package entries in the format * ':?:?' for which Display APIs should never * be constrained. */ private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS = "never_constrain_display_apis"; /** * A boolean flag indicating whether Display APIs should never be constrained for all * packages. If true, {@link #FLAG_NEVER_CONSTRAIN_DISPLAY_APIS} is ignored. */ private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES = "never_constrain_display_apis_all_packages"; /** * A string flag whose value holds a comma separated list of package entries in the format * ':?:?' for which Display APIs should * always be constrained. */ private static final String FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS = "always_constrain_display_apis"; /** * Indicates that display APIs should never be constrained to the activity window bounds for all * packages. */ private boolean mNeverConstrainDisplayApisAllPackages; /** * Indicates that display APIs should never be constrained to the activity window bounds for * a set of defined packages. Map keys are package names, and entries are a * 'Pair(, )'. */ private ArrayMap> mNeverConstrainConfigMap; /** * Indicates that display APIs should always be constrained to the activity window bounds for * a set of defined packages. Map keys are package names, and entries are a * 'Pair(, )'. */ private ArrayMap> mAlwaysConstrainConfigMap; public ConstrainDisplayApisConfig() { updateCache(); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONSTRAIN_DISPLAY_APIS, BackgroundThread.getExecutor(), properties -> updateCache()); } /** * Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the * flag 'never_constrain_display_apis' contains a package entry that matches the given {@code * applicationInfo}. * * @param applicationInfo Information about the application/package. */ public boolean getNeverConstrainDisplayApis(ApplicationInfo applicationInfo) { if (mNeverConstrainDisplayApisAllPackages) { return true; } return flagHasMatchingPackageEntry(mNeverConstrainConfigMap, applicationInfo); } /** * Returns true if the flag 'always_constrain_display_apis' contains a package entry that * matches the given {@code applicationInfo}. * * @param applicationInfo Information about the application/package. */ public boolean getAlwaysConstrainDisplayApis(ApplicationInfo applicationInfo) { return flagHasMatchingPackageEntry(mAlwaysConstrainConfigMap, applicationInfo); } /** * Updates {@link #mNeverConstrainDisplayApisAllPackages}, {@link #mNeverConstrainConfigMap}, * and {@link #mAlwaysConstrainConfigMap} from the {@link DeviceConfig}. */ private void updateCache() { mNeverConstrainDisplayApisAllPackages = DeviceConfig.getBoolean( NAMESPACE_CONSTRAIN_DISPLAY_APIS, FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false); final String neverConstrainConfigStr = DeviceConfig.getString( NAMESPACE_CONSTRAIN_DISPLAY_APIS, FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); mNeverConstrainConfigMap = buildConfigMap(neverConstrainConfigStr); final String alwaysConstrainConfigStr = DeviceConfig.getString( NAMESPACE_CONSTRAIN_DISPLAY_APIS, FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); mAlwaysConstrainConfigMap = buildConfigMap(alwaysConstrainConfigStr); } /** * Processes the configuration string into a map of version codes, for the given * configuration to be applied to the specified packages. If the given package * entry string is invalid, then the map will not contain an entry for the package. * * @param configStr A configuration string expected to be in the format of a list of package * entries separated by ','. A package entry expected to be in the format * ':?:?'. * @return a map of configuration entries, where each key is a package name. Each value is * a pair of version codes, in the format 'Pair(, )'. */ private static ArrayMap> buildConfigMap(String configStr) { ArrayMap> configMap = new ArrayMap<>(); // String#split returns a non-empty array given an empty string. if (configStr.isEmpty()) { return configMap; } for (String packageEntryString : configStr.split(",")) { List packageAndVersions = Arrays.asList(packageEntryString.split(":", 3)); if (packageAndVersions.size() != 3) { Slog.w(TAG, "Invalid package entry in flag 'never/always_constrain_display_apis': " + packageEntryString); // Skip this entry. continue; } String packageName = packageAndVersions.get(0); String minVersionCodeStr = packageAndVersions.get(1); String maxVersionCodeStr = packageAndVersions.get(2); try { final long minVersion = minVersionCodeStr.isEmpty() ? Long.MIN_VALUE : Long.parseLong( minVersionCodeStr); final long maxVersion = maxVersionCodeStr.isEmpty() ? Long.MAX_VALUE : Long.parseLong( maxVersionCodeStr); Pair minMaxVersionCodes = new Pair<>(minVersion, maxVersion); configMap.put(packageName, minMaxVersionCodes); } catch (NumberFormatException e) { Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryString); // Skip this entry. } } return configMap; } /** * Returns true if the flag with the given {@code flagName} contains a package entry that * matches the given {@code applicationInfo}. * * @param configMap the map representing the current configuration value to examine * @param applicationInfo Information about the application/package. */ private static boolean flagHasMatchingPackageEntry(Map> configMap, ApplicationInfo applicationInfo) { if (configMap.isEmpty()) { return false; } if (!configMap.containsKey(applicationInfo.packageName)) { return false; } return matchesApplicationInfo(configMap.get(applicationInfo.packageName), applicationInfo); } /** * Parses the given {@code minMaxVersionCodes} and returns true if {@code * applicationInfo.longVersionCode} is within the version range in the pair. * Returns false otherwise. * * @param minMaxVersionCodes A pair expected to be in the format * 'Pair(, )'. * @param applicationInfo Information about the application/package. */ private static boolean matchesApplicationInfo(Pair minMaxVersionCodes, ApplicationInfo applicationInfo) { return applicationInfo.longVersionCode >= minMaxVersionCodes.first && applicationInfo.longVersionCode <= minMaxVersionCodes.second; } }