Add cts test for power policy group

1. test power policy group related public APIs.
  a. define power policy group
  b. apply power policy group

Bug: 188475561
Test: atest
CtsCarHostTestCases: PowerPolicyHostTest#testPowerPolicyChange
Change-Id: I427602039fa630d0283cc74050e9c88cbf5bd4fd
diff --git a/hostsidetests/car/src/android/car/cts/PowerPolicyHostTest.java b/hostsidetests/car/src/android/car/cts/PowerPolicyHostTest.java
index 46bb5a3..62ab22e 100644
--- a/hostsidetests/car/src/android/car/cts/PowerPolicyHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/PowerPolicyHostTest.java
@@ -20,6 +20,7 @@
 import android.car.cts.powerpolicy.CpmsSystemLayerStateInfo;
 import android.car.cts.powerpolicy.PowerPolicyConstants;
 import android.car.cts.powerpolicy.PowerPolicyDef;
+import android.car.cts.powerpolicy.PowerPolicyGroups;
 import android.car.cts.powerpolicy.PowerPolicyTestHelper;
 import android.car.cts.powerpolicy.SilentModeInfo;
 import android.car.cts.powerpolicy.SystemInfoParser;
@@ -128,9 +129,27 @@
         testHelper = getTestHelper(testcase, 6, teststep);
         testHelper.checkCurrentPolicy(PowerPolicyDef.IdSet.DEFAULT_ALL_ON);
 
+        // add power policy group test here to utilize added test1 and test2 policies
+        teststep = "check default power policy group";
+        PowerPolicyGroups emptyGroups = new PowerPolicyGroups();
+        testHelper = getTestHelper(testcase, 7, teststep);
+        testHelper.checkCurrentPolicyGroupId(null);
+        testHelper.checkPowerPolicyGroups(emptyGroups);
+
+        teststep = "define power policy group";
+        definePowerPolicyGroup(PowerPolicyGroups.TestSet.POLICY_GROUP_DEF1.toShellCommandString());
+        definePowerPolicyGroup(PowerPolicyGroups.TestSet.POLICY_GROUP_DEF2.toShellCommandString());
+        testHelper = getTestHelper(testcase, 8, teststep);
+        testHelper.checkPowerPolicyGroups(PowerPolicyGroups.TestSet.POLICY_GROUPS1);
+
+        teststep = "set power policy group";
+        setPowerPolicyGroup(PowerPolicyGroups.TestSet.GROUP_ID1);
+        testHelper = getTestHelper(testcase, 9, teststep);
+        testHelper.checkCurrentPolicyGroupId(PowerPolicyGroups.TestSet.GROUP_ID1);
+
         rebootDevice();
         teststep = "reboot to clear added test power policies";
-        testHelper = getTestHelper(testcase, 7, teststep);
+        testHelper = getTestHelper(testcase, 10, teststep);
         expectedTotalPolicies = PowerPolicyDef.PolicySet.TOTAL_DEFAULT_REGISTERED_POLICIES;
         testHelper.checkCurrentState(PowerPolicyConstants.CarPowerState.ON);
         testHelper.checkCurrentPolicy(PowerPolicyDef.IdSet.DEFAULT_ALL_ON);
@@ -194,6 +213,14 @@
         executeCommand("cmd car_service apply-power-policy %s", policyId);
     }
 
+    private void definePowerPolicyGroup(String policyGroupStr) throws Exception {
+        executeCommand("cmd car_service define-power-policy-group %s", policyGroupStr);
+    }
+
+    private void setPowerPolicyGroup(String policyGroupId) throws Exception {
+        executeCommand("cmd car_service set-power-policy-group %s", policyGroupId);
+    }
+
     private void waitForDeviceAvailable() throws Exception {
         try {
             getDevice().waitForDeviceAvailable();
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java b/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
index 33ac06a..330ae50 100644
--- a/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
@@ -33,6 +33,8 @@
     public static final String CURRENT_POLICY_ID_HDR = "mCurrentPowerPolicyId:";
     public static final String PENDING_POLICY_ID_HDR = "mPendingPowerPolicyId:";
     public static final String CURRENT_POLICY_GROUP_ID_HDR = "mCurrentPowerPolicyGroupId:";
+    public static final String POWER_POLICY_GROUPS_HDR = "Power policy groups:";
+    public static final String PREEMPTIVE_POWER_POLICY_HDR = "Preemptive power policy:";
     public static final String COMPONENT_STATE_HDR = "Power components state:";
     public static final String COMPONENT_CONTROLLED_HDR =
             "Components powered off by power policy:";
@@ -51,6 +53,7 @@
     private final ArrayList<String> mControlledEnables;
     private final ArrayList<String> mControlledDisables;
     private final String[] mChangedComponents;
+    private final PowerPolicyGroups mPowerPolicyGroups;
     private final String mCurrentPolicyId;
     private final String mPendingPolicyId;
     private final String mCurrentPolicyGroupId;
@@ -61,7 +64,7 @@
 
     private CpmsFrameworkLayerStateInfo(String currentPolicyId, String pendingPolicyId,
             String currentPolicyGroupId, String[] changedComponents,
-            ArrayList<String> enables, ArrayList<String> disables,
+            ArrayList<String> enables, ArrayList<String> disables, PowerPolicyGroups policyGroups,
             ArrayList<String> controlledEnables, ArrayList<String> controlledDisables,
             boolean monitoringHw, boolean silentModeByHw, boolean forcedSilentMode,
             int currentState) {
@@ -70,6 +73,7 @@
         mControlledEnables = controlledEnables;
         mControlledDisables = controlledDisables;
         mChangedComponents = changedComponents;
+        mPowerPolicyGroups = policyGroups;
         mCurrentPolicyId = currentPolicyId;
         mPendingPolicyId = pendingPolicyId;
         mCurrentPolicyGroupId = currentPolicyGroupId;
@@ -103,6 +107,14 @@
         return PowerPolicyDef.PowerComponent.asComponentArray(mDisables);
     }
 
+    public String getCurrentPolicyGroupId() {
+        return mCurrentPolicyGroupId;
+    }
+
+    public PowerPolicyGroups getPowerPolicyGroups() {
+        return mPowerPolicyGroups;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(STRING_BUILDER_BUF_SIZE);
@@ -131,6 +143,7 @@
                 && mForcedSilentMode == that.mForcedSilentMode
                 && mEnables.equals(that.mEnables)
                 && mDisables.equals(that.mDisables)
+                && mPowerPolicyGroups.equals(that.mPowerPolicyGroups)
                 && mControlledEnables.equals(that.mControlledEnables)
                 && mControlledDisables.equals(that.mControlledDisables)
                 && Arrays.equals(mChangedComponents, that.mChangedComponents)
@@ -142,8 +155,9 @@
     @Override
     public int hashCode() {
         return Objects.hash(mEnables, mDisables, mControlledEnables, mControlledDisables,
-                mChangedComponents, mCurrentPolicyId, mPendingPolicyId, mCurrentPolicyGroupId,
-                mCurrentState, mMonitoringHw, mSilentModeByHw, mForcedSilentMode);
+                mChangedComponents, mPowerPolicyGroups, mCurrentPolicyId, mPendingPolicyId,
+                mCurrentPolicyGroupId, mCurrentState, mMonitoringHw, mSilentModeByHw,
+                mForcedSilentMode);
     }
 
     public static CpmsFrameworkLayerStateInfo parse(String cmdOutput) throws Exception {
@@ -156,6 +170,7 @@
         ArrayList<String> controlledEnables = null;
         ArrayList<String> controlledDisables = null;
         String[] changedComponents = null;
+        PowerPolicyGroups policyGroups = null;
         boolean monitoringHw = false;
         boolean silentModeByHw = false;
         boolean forcedSilentMode = false;
@@ -178,6 +193,11 @@
                 case CURRENT_POLICY_GROUP_ID_HDR:
                     currentPolicyGroupId = parser.getStringData(CURRENT_POLICY_GROUP_ID_HDR);
                     break;
+                case POWER_POLICY_GROUPS_HDR:
+                    ArrayList<String> groupList = parser.getStringArray(POWER_POLICY_GROUPS_HDR,
+                            PREEMPTIVE_POWER_POLICY_HDR);
+                    policyGroups = PowerPolicyGroups.parse(groupList);
+                    break;
                 case COMPONENT_STATE_HDR:
                     parser.parseComponentStates(COMPONENT_STATE_HDR,
                             COMPONENT_CONTROLLED_HDR, true);
@@ -220,7 +240,7 @@
         }
 
         return new CpmsFrameworkLayerStateInfo(currentPolicyId, pendingPolicyId,
-                currentPolicyGroupId, changedComponents, enables, disables,
+                currentPolicyGroupId, changedComponents, enables, disables, policyGroups,
                 controlledEnables, controlledDisables, monitoringHw, silentModeByHw,
                 forcedSilentMode, currentState);
     }
@@ -233,6 +253,7 @@
             CURRENT_POLICY_GROUP_ID_HDR,
             COMPONENT_STATE_HDR,
             COMPONENT_CONTROLLED_HDR,
+            POWER_POLICY_GROUPS_HDR,
             COMPONENT_CHANGED_HDR,
             MONITORING_HW_HDR,
             SILENT_MODE_BY_HW_HDR,
@@ -280,6 +301,26 @@
             return val;
         }
 
+        private ArrayList<String> getStringArray(String startHdr, String endHdr)
+                throws Exception {
+            if (!mLines[mIdx].contains(startHdr)) {
+                String errMsg = String.format("expected start header %s at line %d : %s",
+                        startHdr, mIdx, mLines[mIdx]);
+                throw new IllegalArgumentException(errMsg);
+            }
+
+            ArrayList<String> strArray = new ArrayList<String>();
+            while (++mIdx < mLines.length && !mLines[mIdx].contains(endHdr)) {
+                strArray.add(mLines[mIdx]);
+            }
+            mIdx--;
+
+            if (mIdx == (mLines.length - 1)) {
+                throw new IllegalArgumentException("reaches the end while get " + startHdr);
+            }
+            return strArray;
+        }
+
         private void parseComponentStates(String startHdr, String endHdr,
                 boolean hasStateInfo) throws Exception {
             mEnables = new ArrayList<String>();
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyGroups.java b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyGroups.java
new file mode 100644
index 0000000..59969f7
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyGroups.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.powerpolicy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Objects;
+
+public final class PowerPolicyGroups {
+    private final HashMap<String, PowerPolicyGroupDef> mPolicyGroups = new HashMap<>();
+
+    public PowerPolicyGroups() { }
+
+    public PowerPolicyGroups(PowerPolicyGroupDef[] defs) {
+        for (int i = 0; i < defs.length; i++) {
+            mPolicyGroups.put(defs[i].mGroupId, defs[i]);
+        }
+    }
+
+    public void add(String id, String waitForVHALPolicy, String onPolicy) throws Exception {
+        if (mPolicyGroups.containsKey(id)) {
+            throw new IllegalArgumentException(id + " policy group already exists");
+        }
+        PowerPolicyGroupDef groupDef = new PowerPolicyGroupDef(id, waitForVHALPolicy, onPolicy);
+        mPolicyGroups.put(id, groupDef);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder str = new StringBuilder();
+        str.append("Power policy groups:\n");
+        mPolicyGroups.forEach((k, v) -> str.append(v.toString()));
+        return str.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return mPolicyGroups.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PowerPolicyGroups peer = (PowerPolicyGroups) o;
+        return mPolicyGroups.equals(peer.mPolicyGroups);
+    }
+
+    public static PowerPolicyGroups parse(ArrayList<String> defStrs) throws Exception {
+        if ((defStrs.size() % 3) != 0) {
+            throw new IllegalArgumentException("each policy group def needs 3 lines of data");
+        }
+
+        PowerPolicyGroups policyGroups = new PowerPolicyGroups();
+        for (int i = 0; i < defStrs.size(); i += 3) {
+            String groupId = defStrs.get(i).trim();
+            String waitForVHALPolicy = parsePolicyGroupDef("WaitForVHAL", defStrs.get(i + 1));
+            String onPolicy = parsePolicyGroupDef("On", defStrs.get(i + 2));
+            policyGroups.add(groupId, waitForVHALPolicy, onPolicy);
+        }
+        return policyGroups;
+    }
+
+    private static String parsePolicyGroupDef(String stateName, String defStr) throws Exception {
+        String[] tokens = defStr.trim().split("(\\s*)(-{1,2})(>?)(\\s*)");
+        if (tokens.length != 3) {
+            throw new IllegalArgumentException("malformatted policy group def str: " + defStr);
+        }
+
+        if (!stateName.equals(tokens[1].trim())) {
+            String errMsg = String.format("expected power state: %s but got: %s",
+                    stateName, tokens[1]);
+            throw new IllegalArgumentException(errMsg);
+        }
+
+        return tokens[2].trim();
+    }
+
+    public static final class PowerPolicyGroupDef {
+        private final String mGroupId;
+        private final String mWaitForVHALStatePolicy;
+        private final String mOnStatePolicy;
+
+        private PowerPolicyGroupDef(String groupId, String waitForVHALPolicy, String onPolicy) {
+            mGroupId = groupId;
+            mWaitForVHALStatePolicy = waitForVHALPolicy;
+            mOnStatePolicy = onPolicy;
+        }
+
+        public String getGroupId() {
+            return mGroupId;
+        }
+
+        public String getWaitForVHALStatePolicy() {
+            return mWaitForVHALStatePolicy;
+        }
+
+        public String getOnStatePolicy() {
+            return mOnStatePolicy;
+        }
+
+        public String toShellCommandString() {
+            return String.format("%s WaitForVHAL:%s On:%s", mGroupId,
+                    mWaitForVHALStatePolicy, mOnStatePolicy);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder str = new StringBuilder();
+            str.append("  ").append(mGroupId).append('\n');
+            str.append("    - WaitForVHAL --> ").append(mWaitForVHALStatePolicy).append('\n');
+            str.append("    - On --> ").append(mOnStatePolicy).append('\n');
+            return str.toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            PowerPolicyGroupDef that = (PowerPolicyGroupDef) o;
+            return Objects.equals(mGroupId, that.mGroupId)
+                    && Objects.equals(mWaitForVHALStatePolicy, that.mWaitForVHALStatePolicy)
+                    && Objects.equals(mOnStatePolicy, that.mOnStatePolicy);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mGroupId, mWaitForVHALStatePolicy, mOnStatePolicy);
+        }
+    }
+
+    public static final class TestSet {
+        public static final String GROUP_ID1 = "policy_group1";
+        public static final String GROUP_ID2 = "policy_group2";
+
+        public static final PowerPolicyGroupDef POLICY_GROUP_DEF1 =
+                new PowerPolicyGroupDef(GROUP_ID1, PowerPolicyDef.IdSet.TEST1,
+                    PowerPolicyDef.IdSet.TEST2);
+
+        public static final PowerPolicyGroupDef POLICY_GROUP_DEF2 =
+                new PowerPolicyGroupDef(GROUP_ID2, PowerPolicyDef.IdSet.TEST2,
+                    PowerPolicyDef.IdSet.TEST1);
+
+        public static final PowerPolicyGroups POLICY_GROUPS1 = new PowerPolicyGroups(
+                new PowerPolicyGroupDef[]{POLICY_GROUP_DEF1, POLICY_GROUP_DEF2});
+
+        private TestSet() { }
+    }
+}
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestHelper.java b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestHelper.java
index 0e61af9..cb6cf72 100644
--- a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestHelper.java
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestHelper.java
@@ -127,4 +127,17 @@
         assertThat(mFrameCpms.getCurrentDisabledComponents()).asList()
                 .containsExactlyElementsIn(expected.getDisables());
     }
+
+    public void checkCurrentPolicyGroupId(String expected) {
+        if (expected == null) {
+            expected = "null";
+        }
+        assertWithMessage("checkCurrentPolicyGroupId")
+                .that(expected.equals(mFrameCpms.getCurrentPolicyGroupId())).isTrue();
+    }
+
+    public void checkPowerPolicyGroups(PowerPolicyGroups expected) {
+        assertWithMessage("checkPowerPolicyGroups")
+                .that(expected.equals(mFrameCpms.getPowerPolicyGroups())).isTrue();
+    }
 }