1 /*
2  * Copyright (C) 2022 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 com.android.systemui.flags;
18 
19 import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
20 
21 import androidx.annotation.NonNull;
22 
23 import com.android.systemui.statusbar.commandline.Command;
24 
25 import java.io.PrintWriter;
26 import java.util.List;
27 import java.util.Map;
28 
29 import javax.inject.Inject;
30 import javax.inject.Named;
31 
32 /**
33  * A {@link Command} used to flip flags in SystemUI.
34  */
35 public class FlagCommand implements Command {
36     public static final String FLAG_COMMAND = "flag";
37 
38     private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
39     private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
40     private final List<String> mSetCommands = List.of("set", "put");
41     private final FeatureFlagsClassicDebug mFeatureFlags;
42     private final Map<String, Flag<?>> mAllFlags;
43 
44     @Inject
FlagCommand( FeatureFlagsClassicDebug featureFlags, @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags )45     FlagCommand(
46             FeatureFlagsClassicDebug featureFlags,
47             @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags
48     ) {
49         mFeatureFlags = featureFlags;
50         mAllFlags = allFlags;
51     }
52 
53     @Override
execute(@onNull PrintWriter pw, @NonNull List<String> args)54     public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
55         if (args.size() == 0) {
56             pw.println("Error: no flag name supplied");
57             help(pw);
58             pw.println();
59             printKnownFlags(pw);
60             return;
61         }
62 
63         String name = args.get(0);
64         if (!mAllFlags.containsKey(name)) {
65             pw.println("Unknown flag name: " + name);
66             pw.println();
67             printKnownFlags(pw);
68             return;
69         }
70 
71         Flag<?> flag = mAllFlags.get(name);
72 
73         String cmd = "";
74         if (args.size() > 1) {
75             cmd = args.get(1).toLowerCase();
76         }
77 
78         if ("erase".equals(cmd) || "reset".equals(cmd)) {
79             if (args.size() > 2) {
80                 pw.println("Invalid number of arguments to reset a flag.");
81                 help(pw);
82                 return;
83             }
84 
85             mFeatureFlags.eraseFlag(flag);
86             return;
87         }
88 
89         boolean shouldSet = true;
90         if (args.size() == 1) {
91             shouldSet = false;
92         }
93         if (isBooleanFlag(flag)) {
94             if (args.size() > 2) {
95                 pw.println("Invalid number of arguments for a boolean flag.");
96                 help(pw);
97                 return;
98             }
99             boolean newValue = isBooleanFlagEnabled(flag);
100             if ("toggle".equals(cmd)) {
101                 newValue = !newValue;
102             } else if (mOnCommands.contains(cmd)) {
103                 newValue = true;
104             } else if (mOffCommands.contains(cmd)) {
105                 newValue = false;
106             } else if (shouldSet) {
107                 pw.println("Invalid on/off argument supplied");
108                 help(pw);
109                 return;
110             }
111 
112             pw.println("Flag " + name + " is " + newValue);
113             pw.flush();  // Next command will restart sysui, so flush before we do so.
114             if (shouldSet) {
115                 mFeatureFlags.setBooleanFlagInternal(flag, newValue);
116             }
117             return;
118 
119         } else if (isStringFlag(flag)) {
120             if (shouldSet) {
121                 if (args.size() != 3) {
122                     pw.println("Invalid number of arguments a StringFlag.");
123                     help(pw);
124                     return;
125                 } else if (!mSetCommands.contains(cmd)) {
126                     pw.println("Unknown command: " + cmd);
127                     help(pw);
128                     return;
129                 }
130                 String value = args.get(2);
131                 pw.println("Setting Flag " + name + " to " + value);
132                 pw.flush();  // Next command will restart sysui, so flush before we do so.
133                 mFeatureFlags.setStringFlagInternal(flag, args.get(2));
134             } else {
135                 pw.println("Flag " + name + " is " + getStringFlag(flag));
136             }
137             return;
138         } else if (isIntFlag(flag)) {
139             if (shouldSet) {
140                 if (args.size() != 3) {
141                     pw.println("Invalid number of arguments for an IntFlag.");
142                     help(pw);
143                     return;
144                 } else if (!mSetCommands.contains(cmd)) {
145                     pw.println("Unknown command: " + cmd);
146                     help(pw);
147                     return;
148                 }
149                 int value = Integer.parseInt(args.get(2));
150                 pw.println("Setting Flag " + name + " to " + value);
151                 pw.flush();  // Next command will restart sysui, so flush before we do so.
152                 mFeatureFlags.setIntFlagInternal(flag, value);
153             } else {
154                 pw.println("Flag " + name + " is " + getIntFlag(flag));
155             }
156             return;
157         }
158     }
159 
160     @Override
help(PrintWriter pw)161     public void help(PrintWriter pw) {
162         pw.println("Usage: adb shell cmd statusbar flag <id> [options]");
163         pw.println();
164         pw.println("  Boolean Flag Options: "
165                         + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
166         pw.println("  String Flag Options: [set|put \"<value>\"]");
167         pw.println("  Int Flag Options: [set|put <value>]");
168         pw.println();
169         pw.println("The id can either be a numeric integer or the corresponding field name");
170         pw.println(
171                 "If no argument is supplied after the id, the flags runtime value is output");
172     }
173 
isBooleanFlag(Flag<?> flag)174     private boolean isBooleanFlag(Flag<?> flag) {
175         return (flag instanceof BooleanFlag)
176                 || (flag instanceof ResourceBooleanFlag)
177                 || (flag instanceof SysPropFlag);
178     }
179 
isTeamfoodFlag(Flag<?> flag)180     private Boolean isTeamfoodFlag(Flag<?>  flag) {
181         if (!(flag instanceof BooleanFlag)) {
182             return null;
183         }
184         return flag.getTeamfood();
185     }
186 
isBooleanFlagEnabled(Flag<?> flag)187     private boolean isBooleanFlagEnabled(Flag<?> flag) {
188         if (flag instanceof ReleasedFlag) {
189             return mFeatureFlags.isEnabled((ReleasedFlag) flag);
190         } else if (flag instanceof UnreleasedFlag) {
191             return mFeatureFlags.isEnabled((UnreleasedFlag) flag);
192         } else if (flag instanceof ResourceBooleanFlag) {
193             return mFeatureFlags.isEnabled((ResourceBooleanFlag) flag);
194         } else if (flag instanceof SysPropFlag) {
195             return mFeatureFlags.isEnabled((SysPropBooleanFlag) flag);
196         }
197 
198         return false;
199     }
200 
isStringFlag(Flag<?> flag)201     private boolean isStringFlag(Flag<?> flag) {
202         return (flag instanceof StringFlag) || (flag instanceof ResourceStringFlag);
203     }
204 
getStringFlag(Flag<?> flag)205     private String getStringFlag(Flag<?> flag) {
206         if (flag instanceof StringFlag) {
207             return mFeatureFlags.getString((StringFlag) flag);
208         } else if (flag instanceof ResourceStringFlag) {
209             return mFeatureFlags.getString((ResourceStringFlag) flag);
210         }
211 
212         return "";
213     }
214 
isIntFlag(Flag<?> flag)215     private boolean isIntFlag(Flag<?> flag) {
216         return (flag instanceof IntFlag) || (flag instanceof ResourceIntFlag);
217     }
218 
getIntFlag(Flag<?> flag)219     private int getIntFlag(Flag<?> flag) {
220         if (flag instanceof IntFlag) {
221             return mFeatureFlags.getInt((IntFlag) flag);
222         } else if (flag instanceof ResourceIntFlag) {
223             return mFeatureFlags.getInt((ResourceIntFlag) flag);
224         }
225 
226         return 0;
227     }
228 
printKnownFlags(PrintWriter pw)229     private void printKnownFlags(PrintWriter pw) {
230         Map<String, Flag<?>> fields = FlagsFactory.INSTANCE.getKnownFlags();
231 
232         int longestFieldName = 0;
233         for (String fieldName : fields.keySet()) {
234             longestFieldName = Math.max(longestFieldName, fieldName.length());
235         }
236 
237         pw.println("Known Flags:");
238         pw.print("Flag Name");
239         for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
240             pw.print(" ");
241         }
242         pw.print(" Value  ");
243         pw.println(" Teamfood?");
244         for (int i = 0; i < longestFieldName; i++) {
245             pw.print("=");
246         }
247         pw.println(" ======= ===========");
248 
249         for (String fieldName : fields.keySet()) {
250             Flag<?> flag = fields.get(fieldName);
251             if (!mAllFlags.containsKey(flag.getName())) {
252                 continue;
253             }
254             pw.print(fieldName);
255             int fieldWidth = fieldName.length();
256             for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
257                 pw.print(" ");
258             }
259             pw.print(" ");
260             if (isBooleanFlag(flag)) {
261                 boolean enabled = isBooleanFlagEnabled(flag);
262                 pw.print(enabled);
263                 if (enabled) {
264                     pw.print("    ");
265                 } else {
266                     pw.print("   ");
267                 }
268                 Boolean teamfood = isTeamfoodFlag(flag);
269                 if (teamfood != null) {
270                     pw.print(teamfood);
271                 }
272                 pw.println();
273 
274             } else if (isStringFlag(flag)) {
275                 pw.println(getStringFlag(flag));
276             } else if (isIntFlag(flag)) {
277                 pw.println(getIntFlag(flag));
278             } else {
279                 pw.println("<unknown flag type>");
280             }
281         }
282     }
283 }
284