• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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