1 /*
2  * Copyright (C) 2021 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.car.cts.powerpolicy;
18 
19 import com.android.car.power.CarPowerDumpProto;
20 import com.android.car.power.CarPowerDumpProto.PolicyReaderProto;
21 import com.android.car.power.CarPowerDumpProto.PowerComponentHandlerProto;
22 import com.android.car.power.CarPowerDumpProto.PowerComponentHandlerProto.PowerComponentToState;
23 import com.android.car.power.CarPowerDumpProto.SilentModeHandlerProto;
24 import com.android.tradefed.log.LogUtil.CLog;
25 
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 
35 public final class CpmsFrameworkLayerStateInfo {
36     private static final int STRING_BUILDER_BUF_SIZE = 1024;
37     public static final String COMMAND = "dumpsys car_service --services"
38             + " CarPowerManagementService";
39     public static final String COMMAND_PROTO = COMMAND + " --proto";
40     public static final String CURRENT_STATE_HDR = "mCurrentState:";
41     public static final String CURRENT_POLICY_ID_HDR = "mCurrentPowerPolicyId:";
42     public static final String PENDING_POLICY_ID_HDR = "mPendingPowerPolicyId:";
43     public static final String CURRENT_POLICY_GROUP_ID_HDR = "mCurrentPowerPolicyGroupId:";
44     public static final String NUMBER_POLICY_LISTENERS_HDR = "# of power policy change listener:";
45     public static final String POWER_POLICY_GROUPS_HDR = "Power policy groups:";
46     public static final String PREEMPTIVE_POWER_POLICY_HDR = "Preemptive power policy:";
47     public static final String COMPONENT_STATE_HDR = "Power components state:";
48     public static final String COMPONENT_CONTROLLED_HDR =
49             "Components powered off by power policy:";
50     public static final String COMPONENT_CHANGED_HDR = "Components changed by the last policy:";
51     public static final String SILENT_MODE_SUPPORTED = "Silent mode supported:";
52     public static final String MONITORING_HW_HDR = "Monitoring HW state signal:";
53     public static final String SILENT_MODE_BY_HW_HDR = "Silent mode by HW state signal:";
54     public static final String FORCED_SILENT_MODE_HDR = "Forced silent mode:";
55 
56     private static final String[] COMPONENT_LIST = {"AUDIO", "MEDIA", "DISPLAY", "BLUETOOTH",
57             "WIFI", "CELLULAR", "ETHERNET", "PROJECTION", "NFC", "INPUT", "VOICE_INTERACTION",
58             "VISUAL_INTERACTION", "TRUSTED_DEVICE_DETECTION", "LOCATION", "MICROPHONE", "CPU"};
59     private static final HashSet COMPONENT_SET = new HashSet(Arrays.asList(COMPONENT_LIST));
60 
61     private final List<String> mEnables;
62     private final List<String> mDisables;
63     private final List<String> mControlledEnables;
64     private final List<String> mControlledDisables;
65     private final String[] mChangedComponents;
66     private final PowerPolicyGroups mPowerPolicyGroups;
67     private final String mCurrentPolicyId;
68     private final String mPendingPolicyId;
69     private final String mCurrentPolicyGroupId;
70     private final int mNumberPolicyListeners;
71     private final boolean mSilentModeSupported;
72     private final boolean mMonitoringHw;
73     private final boolean mSilentModeByHw;
74     private final boolean mForcedSilentMode;
75     private final int mCurrentState;
76 
CpmsFrameworkLayerStateInfo(String currentPolicyId, String pendingPolicyId, String currentPolicyGroupId, int numberPolicyListeners, String[] changedComponents, List<String> enables, List<String> disables, PowerPolicyGroups policyGroups, List<String> controlledEnables, List<String> controlledDisables, boolean silentModeSupported, boolean monitoringHw, boolean silentModeByHw, boolean forcedSilentMode, int currentState)77     private CpmsFrameworkLayerStateInfo(String currentPolicyId, String pendingPolicyId,
78             String currentPolicyGroupId, int numberPolicyListeners, String[] changedComponents,
79             List<String> enables, List<String> disables, PowerPolicyGroups policyGroups,
80             List<String> controlledEnables, List<String> controlledDisables,
81             boolean silentModeSupported, boolean monitoringHw, boolean silentModeByHw,
82             boolean forcedSilentMode, int currentState) {
83         mEnables = enables;
84         mDisables = disables;
85         mControlledEnables = controlledEnables;
86         mControlledDisables = controlledDisables;
87         mChangedComponents = changedComponents;
88         mPowerPolicyGroups = policyGroups;
89         mCurrentPolicyId = currentPolicyId;
90         mPendingPolicyId = pendingPolicyId;
91         mCurrentPolicyGroupId = currentPolicyGroupId;
92         mNumberPolicyListeners = numberPolicyListeners;
93         mSilentModeSupported = silentModeSupported;
94         mMonitoringHw = monitoringHw;
95         mSilentModeByHw = silentModeByHw;
96         mForcedSilentMode = forcedSilentMode;
97         mCurrentState = currentState;
98     }
99 
getCurrentPolicyId()100     public String getCurrentPolicyId() {
101         return mCurrentPolicyId;
102     }
103 
getPendingPolicyId()104     public String getPendingPolicyId() {
105         return mPendingPolicyId;
106     }
107 
getCurrentState()108     public int getCurrentState() {
109         return mCurrentState;
110     }
111 
isSilentModeSupported()112     public boolean isSilentModeSupported() {
113         return mSilentModeSupported;
114     }
115 
getForcedSilentMode()116     public boolean getForcedSilentMode() {
117         return mForcedSilentMode;
118     }
119 
getCurrentEnabledComponents()120     public PowerPolicyDef.PowerComponent[] getCurrentEnabledComponents() {
121         return PowerPolicyDef.PowerComponent.asComponentArray(mEnables);
122     }
123 
getCurrentDisabledComponents()124     public PowerPolicyDef.PowerComponent[] getCurrentDisabledComponents() {
125         return PowerPolicyDef.PowerComponent.asComponentArray(mDisables);
126     }
127 
getCurrentPolicyGroupId()128     public String getCurrentPolicyGroupId() {
129         return mCurrentPolicyGroupId;
130     }
131 
getPowerPolicyGroups()132     public PowerPolicyGroups getPowerPolicyGroups() {
133         return mPowerPolicyGroups;
134     }
135 
getNumberPolicyListeners()136     public int getNumberPolicyListeners() {
137         return mNumberPolicyListeners;
138     }
139 
isComponentOn(String component)140     public boolean isComponentOn(String component) {
141         return mEnables.contains(component);
142     }
143 
isComponentOff(String component)144     public boolean isComponentOff(String component) {
145         return mDisables.contains(component);
146     }
147 
148     @Override
toString()149     public String toString() {
150         StringBuilder sb = new StringBuilder(STRING_BUILDER_BUF_SIZE);
151         sb.append("mCurrentState=").append(mCurrentState).append(' ');
152         sb.append("mCurrentPolicyId=").append(mCurrentPolicyId).append(' ');
153         sb.append("mPendingPolicyId=").append(mPendingPolicyId).append(' ');
154         sb.append("mCurrentPolicyGroupId=").append(mCurrentPolicyGroupId).append(' ');
155         sb.append("mNumberPolicyListeners=").append(mNumberPolicyListeners).append(' ');
156         sb.append("silentmode=").append(mMonitoringHw).append(',');
157         sb.append(mSilentModeByHw).append(',').append(mForcedSilentMode).append(' ');
158         sb.append("enables=").append(String.join(",", mEnables)).append(' ');
159         sb.append("disables=").append(String.join(",", mDisables)).append(' ');
160         sb.append("controlledEnables=").append(String.join(",", mControlledEnables))
161                 .append(' ');
162         sb.append("controlledDisables=").append(String.join(",", mControlledDisables))
163                 .append(' ');
164         sb.append("changedComponents=").append(String.join(",", mChangedComponents));
165         return sb.toString();
166     }
167 
168     @Override
equals(Object o)169     public boolean equals(Object o) {
170         if (this == o) return true;
171         if (o == null || getClass() != o.getClass()) return false;
172         CpmsFrameworkLayerStateInfo that = (CpmsFrameworkLayerStateInfo) o;
173         return mCurrentState == that.mCurrentState
174                 && mMonitoringHw == that.mMonitoringHw
175                 && mSilentModeByHw == that.mSilentModeByHw
176                 && mForcedSilentMode == that.mForcedSilentMode
177                 && mNumberPolicyListeners == that.mNumberPolicyListeners
178                 && mEnables.equals(that.mEnables)
179                 && mDisables.equals(that.mDisables)
180                 && mPowerPolicyGroups.equals(that.mPowerPolicyGroups)
181                 && mControlledDisables.equals(that.mControlledDisables)
182                 && Arrays.equals(mChangedComponents, that.mChangedComponents)
183                 && Objects.equals(mCurrentPolicyId, that.mCurrentPolicyId)
184                 && Objects.equals(mPendingPolicyId, that.mPendingPolicyId)
185                 && Objects.equals(mCurrentPolicyGroupId, that.mCurrentPolicyGroupId)
186                 && mControlledEnables.equals(that.mControlledEnables);
187     }
188 
189     @Override
hashCode()190     public int hashCode() {
191         return Objects.hash(mEnables, mDisables, mControlledEnables, mControlledDisables,
192                 Arrays.hashCode(mChangedComponents), mPowerPolicyGroups, mCurrentPolicyId,
193                 mPendingPolicyId, mCurrentPolicyGroupId, mCurrentState, mMonitoringHw,
194                 mSilentModeByHw, mForcedSilentMode, mNumberPolicyListeners);
195     }
196 
197     /**
198      * Parse text dump output
199      * @param cmdOutput Text dump output
200      * @return CpmsFrameworkLayerStateInfo representing the information contained within the dump.
201      * @throws IllegalArgumentException if the text dump is incorrectly formatted
202      */
parse(String cmdOutput)203     public static CpmsFrameworkLayerStateInfo parse(String cmdOutput)
204             throws IllegalArgumentException {
205         int currentState = -1;
206         String currentPolicyId = null;
207         String pendingPolicyId = null;
208         String currentPolicyGroupId = null;
209         List<String> enables = null;
210         List<String> disables = null;
211         List<String> controlledEnables = null;
212         List<String> controlledDisables = null;
213         String[] changedComponents = null;
214         PowerPolicyGroups policyGroups = null;
215         boolean silentModeSupported = false;
216         boolean monitoringHw = false;
217         boolean silentModeByHw = false;
218         boolean forcedSilentMode = false;
219         int numberPolicyListeners = 0;
220 
221         String[] lines = cmdOutput.split("\n");
222         StateInfoParser parser = new StateInfoParser(lines);
223         HashSet<String> headerCounter = new HashSet<String>();
224         String header;
225         while ((header = parser.searchHeader()) != null) {
226             switch (header) {
227                 case CURRENT_STATE_HDR:
228                     currentState = parser.getIntData(CURRENT_STATE_HDR);
229                     break;
230                 case CURRENT_POLICY_ID_HDR:
231                     currentPolicyId = parser.getStringData(CURRENT_POLICY_ID_HDR);
232                     break;
233                 case PENDING_POLICY_ID_HDR:
234                     pendingPolicyId = parser.getStringData(PENDING_POLICY_ID_HDR);
235                     break;
236                 case CURRENT_POLICY_GROUP_ID_HDR:
237                     currentPolicyGroupId = parser.getStringData(CURRENT_POLICY_GROUP_ID_HDR);
238                     break;
239                 case POWER_POLICY_GROUPS_HDR:
240                     List<String> groupList = parser.getStringArray(POWER_POLICY_GROUPS_HDR,
241                             PREEMPTIVE_POWER_POLICY_HDR);
242                     policyGroups = PowerPolicyGroups.parse(groupList);
243                     break;
244                 case COMPONENT_STATE_HDR:
245                     parser.parseComponentStates(COMPONENT_STATE_HDR,
246                             COMPONENT_CONTROLLED_HDR, true);
247                     enables = parser.getEnables();
248                     disables = parser.getDisables();
249                     Collections.sort(enables);
250                     Collections.sort(disables);
251                     break;
252                 case COMPONENT_CONTROLLED_HDR:
253                     parser.parseComponentStates(COMPONENT_CONTROLLED_HDR,
254                             COMPONENT_CHANGED_HDR, false);
255                     controlledEnables = parser.getEnables();
256                     controlledDisables = parser.getDisables();
257                     Collections.sort(controlledEnables);
258                     Collections.sort(controlledDisables);
259                     break;
260                 case COMPONENT_CHANGED_HDR:
261                     changedComponents = parser.getChangedComponents(COMPONENT_CHANGED_HDR,
262                             MONITORING_HW_HDR);
263                     break;
264                 case SILENT_MODE_SUPPORTED:
265                     silentModeSupported = parser.getBooleanData(SILENT_MODE_SUPPORTED);
266                     break;
267                 case MONITORING_HW_HDR:
268                     monitoringHw = parser.getBooleanData(MONITORING_HW_HDR);
269                     break;
270                 case SILENT_MODE_BY_HW_HDR:
271                     silentModeByHw = parser.getBooleanData(SILENT_MODE_BY_HW_HDR);
272                     break;
273                 case FORCED_SILENT_MODE_HDR:
274                     forcedSilentMode = parser.getBooleanData(FORCED_SILENT_MODE_HDR);
275                     break;
276                 case NUMBER_POLICY_LISTENERS_HDR:
277                     numberPolicyListeners = parser.getIntData(NUMBER_POLICY_LISTENERS_HDR);
278                     break;
279                 default:
280                     throw new IllegalArgumentException("parser header mismatch: " + header);
281             }
282             headerCounter.add(header);
283         }
284 
285         if (headerCounter.size() != StateInfoParser.HEADERS.length) {
286             String errMsg = "miss headers. got: " + headerCounter + " expected: "
287                     + String.join(",", StateInfoParser.HEADERS);
288             throw new IllegalArgumentException(errMsg);
289         }
290 
291         return new CpmsFrameworkLayerStateInfo(currentPolicyId, pendingPolicyId,
292                 currentPolicyGroupId, numberPolicyListeners, changedComponents, enables,
293                 disables, policyGroups, controlledEnables, controlledDisables, silentModeSupported,
294                 monitoringHw, silentModeByHw, forcedSilentMode, currentState);
295     }
296 
297     private static final class StateInfoParser {
298         private static final String[] HEADERS = {
299                 CURRENT_STATE_HDR,
300                 CURRENT_POLICY_ID_HDR,
301                 PENDING_POLICY_ID_HDR,
302                 CURRENT_POLICY_GROUP_ID_HDR,
303                 NUMBER_POLICY_LISTENERS_HDR,
304                 COMPONENT_STATE_HDR,
305                 COMPONENT_CONTROLLED_HDR,
306                 POWER_POLICY_GROUPS_HDR,
307                 COMPONENT_CHANGED_HDR,
308                 MONITORING_HW_HDR,
309                 SILENT_MODE_BY_HW_HDR,
310                 FORCED_SILENT_MODE_HDR
311         };
312         private final String[] mLines;
313         private List<String> mEnables;
314         private List<String> mDisables;
315         private int mIdx = 0;
316 
StateInfoParser(String[] lines)317         private StateInfoParser(String[] lines) {
318             mLines = lines;
319         }
320 
getEnables()321         private List<String> getEnables() {
322             return mEnables;
323         }
324 
getDisables()325         private List<String> getDisables() {
326             return mDisables;
327         }
328 
getIntData(String header)329         private int getIntData(String header) {
330             int val = 0;
331             switch (header) {
332                 case CURRENT_STATE_HDR:
333                     Pattern pattern = Pattern.compile("mCurrentState: CpmsState "
334                             + "[^\\n]*carPowerStateListenerState=(\\d+)");
335                     Matcher matcher = pattern.matcher(mLines[mIdx]);
336                     if (!matcher.find()) {
337                         throw new IllegalArgumentException("malformatted mCurrentState: "
338                                 + mLines[mIdx]);
339                     }
340                     val = Integer.parseInt(matcher.group(1));
341                     break;
342                 case NUMBER_POLICY_LISTENERS_HDR:
343                     int strLen = mLines[mIdx].length();
344                     val = Integer.parseInt(mLines[mIdx].substring(strLen - 1).trim());
345                     break;
346                 default:
347                     break;
348             }
349             return val;
350         }
351 
getStringData(String header)352         private String getStringData(String header) {
353             String val = null;
354             if (mLines[mIdx].trim().length() != header.length()) {
355                 val = mLines[mIdx].trim().substring(header.length()).trim();
356             }
357             return val;
358         }
359 
getStringArray(String startHdr, String endHdr)360         private List<String> getStringArray(String startHdr, String endHdr) {
361             if (!mLines[mIdx].contains(startHdr)) {
362                 String errMsg = String.format("expected start header %s at line %d : %s",
363                         startHdr, mIdx, mLines[mIdx]);
364                 throw new IllegalArgumentException(errMsg);
365             }
366 
367             ArrayList<String> strArray = new ArrayList<>();
368             while (++mIdx < mLines.length && !mLines[mIdx].contains(endHdr)) {
369                 strArray.add(mLines[mIdx]);
370             }
371             mIdx--;
372 
373             if (mIdx == (mLines.length - 1)) {
374                 throw new IllegalArgumentException("reaches the end while get " + startHdr);
375             }
376             return strArray;
377         }
378 
parseComponentStates(String startHdr, String endHdr, boolean hasStateInfo)379         private void parseComponentStates(String startHdr, String endHdr, boolean hasStateInfo) {
380             mEnables = new ArrayList<>();
381             mDisables = new ArrayList<>();
382             while (mIdx < (mLines.length - 1) && !mLines[++mIdx].contains(endHdr)) {
383                 String stateStr = mLines[mIdx].trim();
384                 String[] vals = stateStr.split(":\\s");
385                 if (hasStateInfo && vals.length != 2) {
386                     String errMsg = String.format("wrong format at %d in: %s ", mIdx, stateStr);
387                     CLog.e(errMsg);
388                     throw new IllegalArgumentException(errMsg);
389                 }
390 
391                 for (int i = 0; i < vals.length; i++) {
392                     vals[i] = vals[i].trim();
393                 }
394 
395                 if (!COMPONENT_SET.contains(vals[0])) {
396                     String errMsg = String.format("invalid component at %d with %s in: %s",
397                             mIdx, vals[0], stateStr);
398                     CLog.e(errMsg);
399                     throw new IllegalArgumentException(errMsg);
400                 }
401 
402                 if (hasStateInfo) {
403                     if (vals[1].startsWith("on")) {
404                         mEnables.add(vals[0]);
405                     } else if (vals[1].startsWith("off")) {
406                         mDisables.add(vals[0]);
407                     } else {
408                         String errMsg =
409                                 String.format("wrong state value at %d with (%s, %s) in: %s",
410                                         mIdx, vals[0], vals[1], stateStr);
411                         CLog.e(errMsg);
412                         throw new IllegalArgumentException(errMsg);
413                     }
414                 } else {
415                     mDisables.add(vals[0]);
416                 }
417             }
418             mIdx--;
419 
420             if (mIdx == (mLines.length - 1)) {
421                 throw new IllegalArgumentException("reaches the end while parse " + startHdr);
422             }
423         }
424 
getChangedComponents(String startHdr, String endHdr)425         private String[] getChangedComponents(String startHdr, String endHdr) {
426             int idx = mLines[mIdx].indexOf(endHdr);
427             String compStr;
428             if (idx < 0) {
429                 compStr = mLines[mIdx].substring(startHdr.length());
430             } else {
431                 compStr = mLines[mIdx].substring(startHdr.length(), idx);
432                 mLines[mIdx] = mLines[mIdx].substring(idx);
433                 mIdx--;
434             }
435             return compStr.split(",\\s*");
436         }
437 
getBooleanData(String header)438         private boolean getBooleanData(String header) {
439             return Boolean.parseBoolean(mLines[mIdx].trim().substring(header.length()).trim());
440         }
441 
searchHeader()442         private String searchHeader() {
443             String header = null;
444             for (mIdx++; mIdx < mLines.length; mIdx++) {
445                 if (mLines[mIdx].trim().isEmpty()) {
446                     continue;
447                 }
448 
449                 int firstHdrPos = mLines[mIdx].length() + 1;
450                 for (int i = 0; i < HEADERS.length; i++) {
451                     int tempHdrPos = mLines[mIdx].indexOf(HEADERS[i]);
452                     if (tempHdrPos >= 0 && (firstHdrPos > tempHdrPos)) {
453                         firstHdrPos = tempHdrPos;
454                         header = HEADERS[i];
455                     }
456                 }
457                 if (header != null) {
458                     break;
459                 }
460             }
461 
462             return header;
463         }
464     }
465 
powerComponentIsValid(String component)466     private static boolean powerComponentIsValid(String component) {
467         if (!COMPONENT_SET.contains(component)) {
468             CLog.e("invalid component " + component);
469             return false;
470         }
471         return true;
472     }
473 
parseProto(CarPowerDumpProto proto)474     public static CpmsFrameworkLayerStateInfo parseProto(CarPowerDumpProto proto) throws Exception {
475         int currentState = proto.getCurrentState().getCarPowerManagerState();
476         String currentPolicyId = proto.getCurrentPowerPolicyId();
477         String pendingPolicyId = proto.getPendingPowerPolicyId();
478         String currentPolicyGroupId = proto.getCurrentPowerPolicyGroupId();
479         PowerComponentHandlerProto componentHandlerProto = proto.getPowerComponentHandler();
480         List<String> enables = new ArrayList<>();
481         List<String> disables = new ArrayList<>();
482 
483         int numComponents = componentHandlerProto.getPowerComponentStateMappingsCount();
484         for (int i = 0; i < numComponents; i++) {
485             PowerComponentToState componentStateMapping =
486                     componentHandlerProto.getPowerComponentStateMappings(i);
487             String powerComponent = componentStateMapping.getPowerComponent();
488             if (powerComponentIsValid(powerComponent)) {
489                 if (componentStateMapping.getState()) {
490                     enables.add(powerComponent);
491                 } else {
492                     disables.add(powerComponent);
493                 }
494             }
495         }
496         Collections.sort(enables);
497         Collections.sort(disables);
498         List<String> controlledDisables = new ArrayList<>();
499 
500         int numComponentsOffByPolicy = componentHandlerProto.getComponentsOffByPolicyCount();
501         for (int i = 0; i < numComponentsOffByPolicy; i++) {
502             String powerComponent = componentHandlerProto.getComponentsOffByPolicy(i);
503             controlledDisables.add(powerComponent);
504         }
505 
506         Collections.sort(controlledDisables);
507         String[] changedComponents =
508                 componentHandlerProto.getLastModifiedComponents().split(",\\s");
509         PolicyReaderProto policyReaderProto = proto.getPolicyReader();
510         CLog.i("policy reader proto exists: " + proto.hasPolicyReader());
511         PowerPolicyGroups policyGroups = PowerPolicyGroups.parseProto(policyReaderProto);
512         SilentModeHandlerProto silentModeProto = proto.getSilentModeHandler();
513         boolean silentModeSupported = silentModeProto.getIsSilentModeSupported();
514         boolean monitoringHw = silentModeProto.getIsMonitoringHwStateSignal();
515         boolean silentModeByHw = silentModeProto.getSilentModeByHwState();
516         boolean forcedSilentMode = silentModeProto.getForcedSilentMode();
517         int numberPolicyListeners = proto.getPowerPolicyListeners();
518 
519         return new CpmsFrameworkLayerStateInfo(currentPolicyId, pendingPolicyId,
520                 currentPolicyGroupId, numberPolicyListeners, changedComponents, enables,
521                 disables, policyGroups, /* controlledEnables= */ new ArrayList<>(),
522                 controlledDisables, silentModeSupported, monitoringHw, silentModeByHw,
523                 forcedSilentMode, currentState);
524     }
525 }
526