diff options
author | Remi NGUYEN VAN <reminv@google.com> | 2020-12-01 18:29:21 +0900 |
---|---|---|
committer | Remi NGUYEN VAN <reminv@google.com> | 2020-12-25 16:23:25 +0900 |
commit | e184c0b3dff7eafba979f60c36787e24f56d4f3c (patch) | |
tree | 40659fa66d3b77589cf94aac1cdd516566269d92 | |
parent | 01645f16980a9ff6d50e1b6aabd7dbcff4791d66 (diff) | |
download | net-e184c0b3dff7eafba979f60c36787e24f56d4f3c.tar.gz |
Split NetworkStackUtils to frameworks/libs/net
Split DeviceConfig and collections-related utilities to CollectionUtils
and DeviceConfigUtils in frameworks/libs/net.
Also add implementations for contains(array, value), to avoid usage of
internal non-API utils.
The original tests are based on mockito-extended, so also add to
NetworkStackTests as a dependency. This also requires setting the test
app as debuggable in its manifest.
Bug: 17454103
Test: atest NetworkStaticLibTests
Change-Id: Ie9bed66a08b7370ebe50d0a7240b788f8b31aa77
8 files changed, 590 insertions, 1 deletions
diff --git a/common/Android.bp b/common/Android.bp index 80a35893..8943453a 100644 --- a/common/Android.bp +++ b/common/Android.bp @@ -143,6 +143,7 @@ filegroup { name: "net-utils-services-common-srcs", srcs: [ "device/android/net/NetworkFactory.java", + "device/com/android/net/module/util/CollectionUtils.java", ], visibility: [ "//frameworks/base/services/net", diff --git a/common/device/com/android/net/module/util/CollectionUtils.java b/common/device/com/android/net/module/util/CollectionUtils.java new file mode 100644 index 00000000..74f738da --- /dev/null +++ b/common/device/com/android/net/module/util/CollectionUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020 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 com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.SparseArray; + +import java.util.Collection; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Utilities for {@link Collection} and arrays. + */ +public final class CollectionUtils { + private CollectionUtils() {} + + /** + * @return True if the array is null or 0-length. + */ + public static <T> boolean isEmpty(@Nullable T[] array) { + return array == null || array.length == 0; + } + + /** + * Returns an int array from the given Integer list. + */ + @NonNull + public static int[] toIntArray(@NonNull Collection<Integer> list) { + int[] array = new int[list.size()]; + int i = 0; + for (Integer item : list) { + array[i] = item; + i++; + } + return array; + } + + /** + * Returns a long array from the given long list. + */ + @NonNull + public static long[] toLongArray(@NonNull Collection<Long> list) { + long[] array = new long[list.size()]; + int i = 0; + for (Long item : list) { + array[i] = item; + i++; + } + return array; + } + + /** + * @return True if there exists at least one element in the sparse array for which + * condition {@code predicate} + */ + public static <T> boolean any(@NonNull SparseArray<T> array, @NonNull Predicate<T> predicate) { + for (int i = 0; i < array.size(); ++i) { + if (predicate.test(array.valueAt(i))) { + return true; + } + } + return false; + } + + /** + * @return true if the array contains the specified value. + */ + public static boolean contains(@Nullable int[] array, int value) { + if (array == null) return false; + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * @return true if the array contains the specified value. + */ + public static <T> boolean contains(@Nullable T[] array, @Nullable T value) { + if (array == null) return false; + for (T element : array) { + if (Objects.equals(element, value)) { + return true; + } + } + return false; + } +} diff --git a/common/device/com/android/net/module/util/DeviceConfigUtils.java b/common/device/com/android/net/module/util/DeviceConfigUtils.java new file mode 100644 index 00000000..5fec0dde --- /dev/null +++ b/common/device/com/android/net/module/util/DeviceConfigUtils.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 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 com.android.net.module.util; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.provider.DeviceConfig; +import android.util.Log; + +import androidx.annotation.BoolRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Utilities for modules to query {@link DeviceConfig} and flags. + */ +public final class DeviceConfigUtils { + private DeviceConfigUtils() {} + + private static final String TAG = DeviceConfigUtils.class.getSimpleName(); + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or has no valid value. + * @return the corresponding value, or defaultValue if none exists. + */ + @Nullable + public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name, + @Nullable String defaultValue) { + String value = DeviceConfig.getProperty(namespace, name); + return value != null ? value : defaultValue; + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or its value is null. + * @return the corresponding value, or defaultValue if none exists. + */ + public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name, + int defaultValue) { + String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */); + try { + return (value != null) ? Integer.parseInt(value) : defaultValue; + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * + * Flags like timeouts should use this method and set an appropriate min/max range: if invalid + * values like "0" or "1" are pushed to devices, everything would timeout. The min/max range + * protects against this kind of breakage. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param minimumValue The minimum value of a property. + * @param maximumValue The maximum value of a property. + * @param defaultValue The value to return if the property does not exist or its value is null. + * @return the corresponding value, or defaultValue if none exists or the fetched value is + * not in the provided range. + */ + public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name, + int minimumValue, int maximumValue, int defaultValue) { + int value = getDeviceConfigPropertyInt(namespace, name, defaultValue); + if (value < minimumValue || value > maximumValue) return defaultValue; + return value; + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or its value is null. + * @return the corresponding value, or defaultValue if none exists. + */ + public static boolean getDeviceConfigPropertyBoolean(@NonNull String namespace, + @NonNull String name, boolean defaultValue) { + String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */); + return (value != null) ? Boolean.parseBoolean(value) : defaultValue; + } + + /** + * Check whether or not one specific experimental feature for a particular namespace from + * {@link DeviceConfig} is enabled by comparing module package version + * with current version of property. If this property version is valid, the corresponding + * experimental feature would be enabled, otherwise disabled. + * + * This is useful to ensure that if a module install is rolled back, flags are not left fully + * rolled out on a version where they have not been well tested. + * @param context The global context information about an app environment. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, + @NonNull String name) { + return isFeatureEnabled(context, namespace, name, false /* defaultEnabled */); + } + + /** + * Check whether or not one specific experimental feature for a particular namespace from + * {@link DeviceConfig} is enabled by comparing module package version + * with current version of property. If this property version is valid, the corresponding + * experimental feature would be enabled, otherwise disabled. + * + * This is useful to ensure that if a module install is rolled back, flags are not left fully + * rolled out on a version where they have not been well tested. + * @param context The global context information about an app environment. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultEnabled The value to return if the property does not exist or its value is + * null. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, + @NonNull String name, boolean defaultEnabled) { + try { + final int propertyVersion = getDeviceConfigPropertyInt(namespace, name, + 0 /* default value */); + final long packageVersion = context.getPackageManager().getPackageInfo( + context.getPackageName(), 0).getLongVersionCode(); + return (propertyVersion == 0 && defaultEnabled) + || (propertyVersion != 0 && packageVersion >= (long) propertyVersion); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Could not find the package name", e); + return false; + } + } + + /** + * Gets boolean config from resources. + */ + public static boolean getResBooleanConfig(@NonNull final Context context, + @BoolRes int configResource, final boolean defaultValue) { + final Resources res = context.getResources(); + try { + return res.getBoolean(configResource); + } catch (Resources.NotFoundException e) { + return defaultValue; + } + } +} diff --git a/common/framework/com/android/net/module/util/ConnectivityUtils.java b/common/framework/com/android/net/module/util/ConnectivityUtils.java new file mode 100644 index 00000000..382912bf --- /dev/null +++ b/common/framework/com/android/net/module/util/ConnectivityUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 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 com.android.net.module.util; + + +import android.annotation.Nullable; + +import java.net.Inet6Address; +import java.net.InetAddress; + +/** + * Various utilities used in connectivity code. + */ +public final class ConnectivityUtils { + private ConnectivityUtils() {} + + + /** + * Return IP address and port in a string format. + */ + public static String addressAndPortToString(InetAddress address, int port) { + return String.format( + (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d", + address.getHostAddress(), port); + } + + /** + * Return true if the provided address is non-null and an IPv6 Unique Local Address (RFC4193). + */ + public static boolean isIPv6ULA(@Nullable InetAddress addr) { + return addr instanceof Inet6Address + && ((addr.getAddress()[0] & 0xfe) == 0xfc); + } + + /** + * Returns the {@code int} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code int} if it is in the range of the {@code int} + * type, {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if + * it is too small + */ + public static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } +} diff --git a/common/tests/unit/Android.bp b/common/tests/unit/Android.bp index 537340ef..c00202e6 100644 --- a/common/tests/unit/Android.bp +++ b/common/tests/unit/Android.bp @@ -9,6 +9,7 @@ android_library { static_libs: [ "net-utils-framework-common", "androidx.test.rules", + "mockito-target-extended-minus-junit4", "net-utils-device-common", "net-tests-utils-host-device-common", ], @@ -29,6 +30,11 @@ android_test { static_libs: [ "NetworkStaticLibTestsLib", ], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], jarjar_rules: "jarjar-rules.txt", test_suites: ["device-tests"], } diff --git a/common/tests/unit/AndroidManifest.xml b/common/tests/unit/AndroidManifest.xml index c747c8a5..84a20a27 100644 --- a/common/tests/unit/AndroidManifest.xml +++ b/common/tests/unit/AndroidManifest.xml @@ -19,7 +19,7 @@ <uses-permission android:name="android.permission.INTERNET" /> - <application> + <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/common/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java new file mode 100644 index 00000000..8af01965 --- /dev/null +++ b/common/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 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 com.android.net.module.util; + +import static android.net.InetAddresses.parseNumericAddress; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for ConnectivityUtils */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConnectivityUtilsTest { + @Test + public void testIsIPv6ULA() { + assertTrue(isIPv6ULA(parseNumericAddress("fc00::"))); + assertTrue(isIPv6ULA(parseNumericAddress("fc00::1"))); + assertTrue(isIPv6ULA(parseNumericAddress("fc00:1234::5678"))); + assertTrue(isIPv6ULA(parseNumericAddress("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + assertFalse(isIPv6ULA(parseNumericAddress("fe00::"))); + assertFalse(isIPv6ULA(parseNumericAddress("2480:1248::123:456"))); + } +} diff --git a/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java new file mode 100644 index 00000000..bb78854c --- /dev/null +++ b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2019 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 com.android.net.module.util; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.provider.DeviceConfig; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; + + +/** + * Tests for DeviceConfigUtils. + * + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DeviceConfigUtilsTest { + private static final String TEST_NAME_SPACE = "connectivity"; + private static final String TEST_EXPERIMENT_FLAG = "experiment_flag"; + private static final int TEST_FLAG_VALUE = 28; + private static final String TEST_FLAG_VALUE_STRING = "28"; + private static final int TEST_DEFAULT_FLAG_VALUE = 0; + private static final int TEST_MAX_FLAG_VALUE = 1000; + private static final int TEST_MIN_FLAG_VALUE = 100; + private static final long TEST_PACKAGE_VERSION = 290000000; + private static final String TEST_PACKAGE_NAME = "test.package.name"; + private MockitoSession mSession; + + @Mock private Context mContext; + @Mock private PackageManager mPm; + @Mock private PackageInfo mPi; + @Mock private Resources mResources; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mSession = mockitoSession().spyStatic(DeviceConfig.class).startMocking(); + + final PackageInfo pi = new PackageInfo(); + pi.setLongVersionCode(TEST_PACKAGE_VERSION); + + doReturn(mPm).when(mContext).getPackageManager(); + doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName(); + doReturn(pi).when(mPm).getPackageInfo(anyString(), anyInt()); + doReturn(mResources).when(mContext).getResources(); + } + + @After + public void tearDown() { + mSession.finishMocking(); + } + + @Test + public void testGetDeviceConfigPropertyInt_Null() { + doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_NotNull() { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_NormalValue() { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, 0 /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_NullValue() { + doReturn(null).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, 0 /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_OverMaximumValue() { + doReturn(Integer.toString(TEST_MAX_FLAG_VALUE + 10)).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_BelowMinimumValue() { + doReturn(Integer.toString(TEST_MIN_FLAG_VALUE - 10)).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyBoolean_Null() { + doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertFalse(DeviceConfigUtils.getDeviceConfigPropertyBoolean( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + false /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyBoolean_NotNull() { + doReturn("true").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertTrue(DeviceConfigUtils.getDeviceConfigPropertyBoolean( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + false /* default value */)); + } + + @Test + public void testFeatureIsEnabledWithExceptionsEnabled() { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testFeatureIsNotEnabled() { + doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testFeatureIsEnabledWithException() throws Exception { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt()); + assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testGetResBooleanConfig() { + final int someResId = 1234; + doReturn(true).when(mResources).getBoolean(someResId); + assertTrue(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false)); + doReturn(false).when(mResources).getBoolean(someResId); + assertFalse(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false)); + doThrow(new Resources.NotFoundException()).when(mResources).getBoolean(someResId); + assertFalse(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false)); + } +} |