| /* |
| * 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 |
| * '<package-name>:<min-version-code>?:<max-version-code>?' 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 |
| * '<package-name>:<min-version-code>?:<max-version-code>?' 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(<min-version-code>, <max-version-code>)'. |
| */ |
| private ArrayMap<String, Pair<Long, Long>> 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(<min-version-code>, <max-version-code>)'. |
| */ |
| private ArrayMap<String, Pair<Long, Long>> 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 |
| * '<package-name>:<min-version-code>?:<max-version-code>?'. |
| * @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(<min-version-code>, <max-version-code>)'. |
| */ |
| private static ArrayMap<String, Pair<Long, Long>> buildConfigMap(String configStr) { |
| ArrayMap<String, Pair<Long, Long>> 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<String> 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<Long, Long> 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<String, Pair<Long, Long>> 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(<min-version-code>, <max-version-code>)'. |
| * @param applicationInfo Information about the application/package. |
| */ |
| private static boolean matchesApplicationInfo(Pair<Long, Long> minMaxVersionCodes, |
| ApplicationInfo applicationInfo) { |
| return applicationInfo.longVersionCode >= minMaxVersionCodes.first |
| && applicationInfo.longVersionCode <= minMaxVersionCodes.second; |
| } |
| } |