• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.net.module.util;
18 
19 import android.content.Context;
20 import android.content.pm.ModuleInfo;
21 import android.content.pm.PackageManager;
22 import android.content.res.Resources;
23 import android.provider.DeviceConfig;
24 import android.util.Log;
25 
26 import androidx.annotation.BoolRes;
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.annotation.VisibleForTesting;
30 
31 /**
32  * Utilities for modules to query {@link DeviceConfig} and flags.
33  */
34 public final class DeviceConfigUtils {
DeviceConfigUtils()35     private DeviceConfigUtils() {}
36 
37     private static final String TAG = DeviceConfigUtils.class.getSimpleName();
38     /**
39      * DO NOT MODIFY: this may be used by multiple modules that will not see the updated value
40      * until they are recompiled, so modifying this constant means that different modules may
41      * be referencing a different tethering module variant, or having a stale reference.
42      */
43     public static final String TETHERING_MODULE_NAME = "com.android.tethering";
44 
45     @VisibleForTesting
resetPackageVersionCacheForTest()46     public static void resetPackageVersionCacheForTest() {
47         sPackageVersion = -1;
48         sModuleVersion = -1;
49     }
50 
51     private static volatile long sPackageVersion = -1;
getPackageVersion(@onNull final Context context)52     private static long getPackageVersion(@NonNull final Context context)
53             throws PackageManager.NameNotFoundException {
54         // sPackageVersion may be set by another thread just after this check, but querying the
55         // package version several times on rare occasions is fine.
56         if (sPackageVersion >= 0) {
57             return sPackageVersion;
58         }
59         final long version = context.getPackageManager().getPackageInfo(
60                 context.getPackageName(), 0).getLongVersionCode();
61         sPackageVersion = version;
62         return version;
63     }
64 
65     /**
66      * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
67      * @param namespace The namespace containing the property to look up.
68      * @param name The name of the property to look up.
69      * @param defaultValue The value to return if the property does not exist or has no valid value.
70      * @return the corresponding value, or defaultValue if none exists.
71      */
72     @Nullable
getDeviceConfigProperty(@onNull String namespace, @NonNull String name, @Nullable String defaultValue)73     public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name,
74             @Nullable String defaultValue) {
75         String value = DeviceConfig.getProperty(namespace, name);
76         return value != null ? value : defaultValue;
77     }
78 
79     /**
80      * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
81      * @param namespace The namespace containing the property to look up.
82      * @param name The name of the property to look up.
83      * @param defaultValue The value to return if the property does not exist or its value is null.
84      * @return the corresponding value, or defaultValue if none exists.
85      */
getDeviceConfigPropertyInt(@onNull String namespace, @NonNull String name, int defaultValue)86     public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
87             int defaultValue) {
88         String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */);
89         try {
90             return (value != null) ? Integer.parseInt(value) : defaultValue;
91         } catch (NumberFormatException e) {
92             return defaultValue;
93         }
94     }
95 
96     /**
97      * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
98      *
99      * Flags like timeouts should use this method and set an appropriate min/max range: if invalid
100      * values like "0" or "1" are pushed to devices, everything would timeout. The min/max range
101      * protects against this kind of breakage.
102      * @param namespace The namespace containing the property to look up.
103      * @param name The name of the property to look up.
104      * @param minimumValue The minimum value of a property.
105      * @param maximumValue The maximum value of a property.
106      * @param defaultValue The value to return if the property does not exist or its value is null.
107      * @return the corresponding value, or defaultValue if none exists or the fetched value is
108      *         not in the provided range.
109      */
getDeviceConfigPropertyInt(@onNull String namespace, @NonNull String name, int minimumValue, int maximumValue, int defaultValue)110     public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
111             int minimumValue, int maximumValue, int defaultValue) {
112         int value = getDeviceConfigPropertyInt(namespace, name, defaultValue);
113         if (value < minimumValue || value > maximumValue) return defaultValue;
114         return value;
115     }
116 
117     /**
118      * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
119      * @param namespace The namespace containing the property to look up.
120      * @param name The name of the property to look up.
121      * @param defaultValue The value to return if the property does not exist or its value is null.
122      * @return the corresponding value, or defaultValue if none exists.
123      */
getDeviceConfigPropertyBoolean(@onNull String namespace, @NonNull String name, boolean defaultValue)124     public static boolean getDeviceConfigPropertyBoolean(@NonNull String namespace,
125             @NonNull String name, boolean defaultValue) {
126         String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */);
127         return (value != null) ? Boolean.parseBoolean(value) : defaultValue;
128     }
129 
130     /**
131      * Check whether or not one specific experimental feature for a particular namespace from
132      * {@link DeviceConfig} is enabled by comparing module package version
133      * with current version of property. If this property version is valid, the corresponding
134      * experimental feature would be enabled, otherwise disabled.
135      *
136      * This is useful to ensure that if a module install is rolled back, flags are not left fully
137      * rolled out on a version where they have not been well tested.
138      * @param context The global context information about an app environment.
139      * @param namespace The namespace containing the property to look up.
140      * @param name The name of the property to look up.
141      * @return true if this feature is enabled, or false if disabled.
142      */
isFeatureEnabled(@onNull Context context, @NonNull String namespace, @NonNull String name)143     public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
144             @NonNull String name) {
145         return isFeatureEnabled(context, namespace, name, false /* defaultEnabled */);
146     }
147 
148     /**
149      * Check whether or not one specific experimental feature for a particular namespace from
150      * {@link DeviceConfig} is enabled by comparing module package version
151      * with current version of property. If this property version is valid, the corresponding
152      * experimental feature would be enabled, otherwise disabled.
153      *
154      * This is useful to ensure that if a module install is rolled back, flags are not left fully
155      * rolled out on a version where they have not been well tested.
156      * @param context The global context information about an app environment.
157      * @param namespace The namespace containing the property to look up.
158      * @param name The name of the property to look up.
159      * @param defaultEnabled The value to return if the property does not exist or its value is
160      *                       null.
161      * @return true if this feature is enabled, or false if disabled.
162      */
isFeatureEnabled(@onNull Context context, @NonNull String namespace, @NonNull String name, boolean defaultEnabled)163     public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
164             @NonNull String name, boolean defaultEnabled) {
165         try {
166             final long packageVersion = getPackageVersion(context);
167             return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
168         } catch (PackageManager.NameNotFoundException e) {
169             Log.e(TAG, "Could not find the package name", e);
170             return false;
171         }
172     }
173 
174     /**
175      * Check whether or not one specific experimental feature for a particular namespace from
176      * {@link DeviceConfig} is enabled by comparing module package version
177      * with current version of property. If this property version is valid, the corresponding
178      * experimental feature would be enabled, otherwise disabled.
179      *
180      * This is useful to ensure that if a module install is rolled back, flags are not left fully
181      * rolled out on a version where they have not been well tested.
182      * @param context The global context information about an app environment.
183      * @param namespace The namespace containing the property to look up.
184      * @param name The name of the property to look up.
185      * @param moduleName The mainline module name which is released as apex.
186      * @param defaultEnabled The value to return if the property does not exist or its value is
187      *                       null.
188      * @return true if this feature is enabled, or false if disabled.
189      */
isFeatureEnabled(@onNull Context context, @NonNull String namespace, @NonNull String name, @NonNull String moduleName, boolean defaultEnabled)190     public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
191             @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
192         try {
193             final long packageVersion = getModuleVersion(context, moduleName);
194             return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
195         } catch (PackageManager.NameNotFoundException e) {
196             Log.e(TAG, "Could not find the module name", e);
197             return false;
198         }
199     }
200 
maybeUseFixedPackageVersion(@onNull Context context)201     private static boolean maybeUseFixedPackageVersion(@NonNull Context context) {
202         final String packageName = context.getPackageName();
203         if (packageName == null) return false;
204 
205         return packageName.equals("com.android.networkstack.tethering")
206                 || packageName.equals("com.android.networkstack.tethering.inprocess");
207     }
208 
isFeatureEnabled(@onNull Context context, long packageVersion, @NonNull String namespace, String name, boolean defaultEnabled)209     private static boolean isFeatureEnabled(@NonNull Context context, long packageVersion,
210             @NonNull String namespace, String name, boolean defaultEnabled)
211             throws PackageManager.NameNotFoundException {
212         final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
213                 0 /* default value */);
214         return (propertyVersion == 0 && defaultEnabled)
215                 || (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
216     }
217 
218     private static volatile long sModuleVersion = -1;
219     @VisibleForTesting public static long FIXED_PACKAGE_VERSION = 10;
getModuleVersion(@onNull Context context, @NonNull String moduleName)220     private static long getModuleVersion(@NonNull Context context, @NonNull String moduleName)
221             throws PackageManager.NameNotFoundException {
222         if (sModuleVersion >= 0) return sModuleVersion;
223 
224         final PackageManager packageManager = context.getPackageManager();
225         ModuleInfo module;
226         try {
227             module = packageManager.getModuleInfo(
228                     moduleName, PackageManager.MODULE_APEX_NAME);
229         } catch (PackageManager.NameNotFoundException e) {
230             // The error may happen if mainline module meta data is not installed e.g. there are
231             // no meta data configuration in AOSP build. To be able to enable a feature in AOSP
232             // by setting a flag via ADB for example. set a small non-zero fixed number for
233             // comparing.
234             if (maybeUseFixedPackageVersion(context)) {
235                 sModuleVersion = FIXED_PACKAGE_VERSION;
236                 return FIXED_PACKAGE_VERSION;
237             } else {
238                 throw e;
239             }
240         }
241         String modulePackageName = module.getPackageName();
242         if (modulePackageName == null) throw new PackageManager.NameNotFoundException(moduleName);
243         final long version = packageManager.getPackageInfo(modulePackageName,
244                 PackageManager.MATCH_APEX).getLongVersionCode();
245         sModuleVersion = version;
246 
247         return version;
248     }
249 
250     /**
251      * Gets boolean config from resources.
252      */
getResBooleanConfig(@onNull final Context context, @BoolRes int configResource, final boolean defaultValue)253     public static boolean getResBooleanConfig(@NonNull final Context context,
254             @BoolRes int configResource, final boolean defaultValue) {
255         final Resources res = context.getResources();
256         try {
257             return res.getBoolean(configResource);
258         } catch (Resources.NotFoundException e) {
259             return defaultValue;
260         }
261     }
262 }
263