1 /* 2 * Copyright (C) 2023 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.platform.test.flag.junit; 18 19 import android.annotation.SuppressLint; 20 import android.app.UiAutomation; 21 import android.platform.test.flag.util.Flag; 22 import android.platform.test.flag.util.FlagReadException; 23 import android.provider.DeviceConfig; 24 25 import androidx.test.platform.app.InstrumentationRegistry; 26 27 import com.google.common.base.CaseFormat; 28 29 import java.lang.reflect.InvocationTargetException; 30 import java.lang.reflect.Method; 31 import java.util.Set; 32 33 /** A {@code IFlagsValueProvider} which provides flag values from device side. */ 34 public class DeviceFlagsValueProvider implements IFlagsValueProvider { 35 private static final String READ_DEVICE_CONFIG_PERMISSION = 36 "android.permission.READ_DEVICE_CONFIG"; 37 38 private static final Set<String> VALID_BOOLEAN_VALUE = Set.of("true", "false"); 39 40 private final UiAutomation mUiAutomation; 41 createCheckFlagsRule()42 public static CheckFlagsRule createCheckFlagsRule() { 43 return new CheckFlagsRule(new DeviceFlagsValueProvider()); 44 } 45 46 /** 47 * Creates a {@link CheckFlagsRule} with an existing {@link UiAutomation} instance. 48 * 49 * <p>This is necessary if the test modifies {@link UiAutomation} flags. 50 * 51 * @param uiAutomation The {@link UiAutomation} used by the test. 52 */ createCheckFlagsRule(UiAutomation uiAutomation)53 public static CheckFlagsRule createCheckFlagsRule(UiAutomation uiAutomation) { 54 return new CheckFlagsRule(new DeviceFlagsValueProvider(uiAutomation)); 55 } 56 DeviceFlagsValueProvider()57 public DeviceFlagsValueProvider() { 58 mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 59 } 60 DeviceFlagsValueProvider(UiAutomation uiAutomation)61 private DeviceFlagsValueProvider(UiAutomation uiAutomation) { 62 mUiAutomation = uiAutomation; 63 } 64 65 @Override setUp()66 public void setUp() { 67 mUiAutomation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG_PERMISSION); 68 } 69 70 @Override getBoolean(String flag)71 public boolean getBoolean(String flag) throws FlagReadException { 72 Flag parsedFlag = Flag.createFlag(flag); 73 if (parsedFlag.namespace() != null) { 74 if (parsedFlag.packageName() != null) { 75 throw new FlagReadException( 76 flag, "You can not specify the namespace for aconfig flags."); 77 } 78 return getLegacyFlagBoolean(parsedFlag); 79 } 80 if (parsedFlag.packageName() == null) { 81 throw new FlagReadException( 82 flag, "Flag name is not the expected format {packageName}.{flagName}."); 83 } 84 String className = parsedFlag.flagsClassName(); 85 String simpleFlagName = parsedFlag.simpleFlagName(); 86 87 // Must be consistent with method name in aconfig auto generated code. 88 String methodName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, simpleFlagName); 89 90 try { 91 Class<?> flagsClass = Class.forName(className); 92 Method method = flagsClass.getMethod(methodName); 93 Object result = method.invoke(null); 94 if (result instanceof Boolean) { 95 return (Boolean) result; 96 } 97 throw new FlagReadException( 98 flag, 99 String.format( 100 "Flag type is %s, not boolean", result.getClass().getSimpleName())); 101 } catch (ClassNotFoundException e) { 102 throw new FlagReadException( 103 flag, 104 String.format( 105 "Can not load the Flags class %s to get its values. Please check the " 106 + "flag name and ensure that the aconfig auto generated " 107 + "library is in the dependency.", 108 className), 109 e); 110 } catch (NoSuchMethodException e) { 111 throw new FlagReadException( 112 flag, 113 String.format( 114 "No method %s in the Flags class to read the flag value. Please check" 115 + " the flag name.", 116 methodName), 117 e); 118 } catch (InvocationTargetException | IllegalAccessException e) { 119 throw new FlagReadException(flag, e); 120 } 121 } 122 getLegacyFlagBoolean(Flag flag)123 private boolean getLegacyFlagBoolean(Flag flag) throws FlagReadException { 124 @SuppressLint("MissingPermission") 125 String property = DeviceConfig.getProperty(flag.namespace(), flag.fullFlagName()); 126 if (property == null) { 127 throw new FlagReadException(flag.fullFlagName(), "Flag does not exist on the device."); 128 } 129 if (VALID_BOOLEAN_VALUE.contains(property)) { 130 return Boolean.valueOf(property); 131 } 132 throw new FlagReadException( 133 flag.fullFlagName(), String.format("Value %s is not a valid boolean.", property)); 134 } 135 136 @Override tearDownBeforeTest()137 public void tearDownBeforeTest() { 138 mUiAutomation.dropShellPermissionIdentity(); 139 } 140 } 141