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.host; 18 19 import android.aconfig.Aconfig.flag_permission; 20 import android.aconfig.Aconfig.flag_state; 21 import android.aconfig.Aconfig.parsed_flags; 22 import android.platform.test.flag.util.Flag; 23 import android.platform.test.flag.util.FlagReadException; 24 25 import com.android.tradefed.device.DeviceNotAvailableException; 26 import com.android.tradefed.device.ITestDevice; 27 import com.android.tradefed.log.LogUtil; 28 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 37 import javax.annotation.Nullable; 38 39 /** 40 * Dumps flags from a give device. 41 * 42 * <p>There are two sources to dump the flag values: device_config and aconfig dumped output. The 43 * device_config contains the ground truth for readwrite flags (including legacy flags), while the 44 * aconfig dumped output contains the ground truth for all readonly flags. 45 * 46 * <p>The {packageName}.{flagName} (we call it full flag name) is the unique identity of each 47 * aconfig flag, which is also the constant value for each flag name in the aconfig auto generated 48 * library. However, each aconfig flag definitions also contains namespace which is in aconfig 49 * dumped output. The {@code DeviceFlags} saves all flag values into a map with the key always 50 * starts with "{namespace}/" no matter it is a legacy flag or aconfig flag, and gets flag values 51 * from this map. 52 */ 53 public class DeviceFlags { 54 private static final String DUMP_DEVICE_CONFIG_CMD = "device_config list"; 55 56 /** 57 * Partitions that contain the aconfig_flags.pb files. Should be consistent with the partitions 58 * defined in core/packaging/flags.mk. 59 */ 60 private static final List<String> FLAG_PARTITIONS = 61 List.of("product", "system", "system_ext", "vendor"); 62 63 /** 64 * The key is the flag name with namespace ({namespace}/{flagName} for legacy flags, 65 * {namespace}/{packageName}.{flagName} for aconfig flags. 66 */ 67 private final Map<String, String> mAllFlagValues = new HashMap<>(); 68 69 /** 70 * The key is the aconfig full flag name ({packageName}.{flagName}), the value is the flag name 71 * with namespace ({namespace}/{packageName}.{flagName}). 72 */ 73 private final Map<String, String> mFlagNameWithNamespaces = new HashMap<>(); 74 DeviceFlags()75 private DeviceFlags() {} 76 createDeviceFlags(ITestDevice testDevice)77 public static DeviceFlags createDeviceFlags(ITestDevice testDevice) throws FlagReadException { 78 DeviceFlags deviceFlags = new DeviceFlags(); 79 deviceFlags.init(testDevice); 80 return deviceFlags; 81 } 82 83 @Nullable getFlagValue(String flag)84 public String getFlagValue(String flag) { 85 String flagWithNamespace = 86 flag.contains(Flag.NAMESPACE_FLAG_SEPARATOR) 87 ? flag 88 : mFlagNameWithNamespaces.get(flag); 89 90 return mAllFlagValues.get(flagWithNamespace); 91 } 92 init(ITestDevice testDevice)93 public void init(ITestDevice testDevice) throws FlagReadException { 94 mAllFlagValues.clear(); 95 mFlagNameWithNamespaces.clear(); 96 LogUtil.CLog.i("Dumping all flag values from the device ..."); 97 getDeviceConfigFlags(testDevice); 98 getAconfigFlags(testDevice); 99 LogUtil.CLog.i("Dumped all flag values from the device."); 100 } 101 getDeviceConfigFlags(ITestDevice testDevice)102 private void getDeviceConfigFlags(ITestDevice testDevice) throws FlagReadException { 103 try { 104 String deviceConfigList = testDevice.executeShellCommand(DUMP_DEVICE_CONFIG_CMD); 105 for (String deviceConfig : deviceConfigList.split("\n")) { 106 if (deviceConfig.contains("=")) { 107 String[] flagNameValue = deviceConfig.split("=", 2); 108 mAllFlagValues.put(flagNameValue[0], flagNameValue[1]); 109 } else { 110 LogUtil.CLog.w("Invalid device config %s", deviceConfig); 111 } 112 } 113 } catch (DeviceNotAvailableException e) { 114 throw new FlagReadException("ALL_FLAGS", e); 115 } 116 } 117 getAconfigFlags(ITestDevice testDevice)118 private void getAconfigFlags(ITestDevice testDevice) throws FlagReadException { 119 getAconfigParsedFlags(testDevice) 120 .getParsedFlagList() 121 .forEach( 122 parsedFlag -> { 123 String namespace = parsedFlag.getNamespace(); 124 String fullFlagName = 125 String.format( 126 Flag.ACONFIG_FULL_FLAG_FORMAT, 127 parsedFlag.getPackage(), 128 parsedFlag.getName()); 129 String flagWithNamespace = 130 String.format( 131 Flag.FLAG_WITH_NAMESPACE_FORMAT, 132 namespace, 133 fullFlagName); 134 mFlagNameWithNamespaces.put(fullFlagName, flagWithNamespace); 135 String defaultValue = 136 parsedFlag.getState().equals(flag_state.ENABLED) 137 ? "true" 138 : "false"; 139 if (parsedFlag.getPermission().equals(flag_permission.READ_ONLY) 140 || !mAllFlagValues.containsKey(flagWithNamespace)) { 141 mAllFlagValues.put(flagWithNamespace, defaultValue); 142 } 143 }); 144 } 145 getAconfigParsedFlags(ITestDevice testDevice)146 private parsed_flags getAconfigParsedFlags(ITestDevice testDevice) throws FlagReadException { 147 parsed_flags.Builder builder = parsed_flags.newBuilder(); 148 149 for (String flagPartition : FLAG_PARTITIONS) { 150 try { 151 String aconfigFlagsPbFilePath = 152 String.format("/%s/etc/aconfig_flags.pb", flagPartition); 153 if (!testDevice.doesFileExist(aconfigFlagsPbFilePath)) { 154 LogUtil.CLog.i("Aconfig flags file %s does not exist", aconfigFlagsPbFilePath); 155 continue; 156 } 157 File aconfigFlagsPbFile = testDevice.pullFile(aconfigFlagsPbFilePath); 158 if (aconfigFlagsPbFile.length() <= 1) { 159 LogUtil.CLog.i("Aconfig flags file %s is empty", aconfigFlagsPbFilePath); 160 continue; 161 } 162 InputStream aconfigFlagsPbInputStream = new FileInputStream(aconfigFlagsPbFile); 163 parsed_flags parsedFlags = parsed_flags.parseFrom(aconfigFlagsPbInputStream); 164 builder.addAllParsedFlag(parsedFlags.getParsedFlagList()); 165 } catch (DeviceNotAvailableException | IOException e) { 166 throw new FlagReadException("ALL_FLAGS", e); 167 } 168 } 169 return builder.build(); 170 } 171 } 172