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