Read vendor power policy in CarService

- PolicyReader reads /vendor/etc/power_policy.xml.
- If the file doesn't exist, PolicyReader starts with the default
configuration.
- Unit test will come in the following CL.

Bug: 170158130
Test: build okay
Change-Id: I601f3d6e28ab94091171da5afa7477505b576e4b
diff --git a/service/Android.bp b/service/Android.bp
index 0d5dab2..a1a8a8f 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -32,6 +32,7 @@
 common_lib_deps = [
     "android.car.userlib",
     "android.car.watchdoglib",
+    "android.frameworks.automotive.powerpolicy-java",
     "android.hidl.base-V1.0-java",
     "android.hardware.automotive.audiocontrol-V1.0-java",
     "android.hardware.automotive.audiocontrol-V2.0-java",
diff --git a/service/src/com/android/car/power/CarPowerManagementService.java b/service/src/com/android/car/power/CarPowerManagementService.java
index 715b0ff..38951e0 100644
--- a/service/src/com/android/car/power/CarPowerManagementService.java
+++ b/service/src/com/android/car/power/CarPowerManagementService.java
@@ -181,6 +181,8 @@
     private boolean mConnectionInProgress;
     private BinderHandler mBinderHandler;
 
+    private final PolicyReader mPolicyReader = new PolicyReader();
+
     private class PowerManagerCallbackList extends RemoteCallbackList<ICarPowerStateListener> {
         /**
          * Old version of {@link #onCallbackDied(E, Object)} that
@@ -246,6 +248,7 @@
 
     @Override
     public void init() {
+        mPolicyReader.init();
         mHal.setListener(this);
         if (mHal.isPowerStateSupported()) {
             // Initialize CPMS in WAIT_FOR_VHAL state
@@ -292,6 +295,7 @@
             writer.println(",mRebootAfterGarageMode:" + mRebootAfterGarageMode);
             writer.println("mSwitchGuestUserBeforeSleep:" + mSwitchGuestUserBeforeSleep);
         }
+        mPolicyReader.dump(writer);
     }
 
     @Override
diff --git a/service/src/com/android/car/power/PolicyReader.java b/service/src/com/android/car/power/PolicyReader.java
new file mode 100644
index 0000000..344707d
--- /dev/null
+++ b/service/src/com/android/car/power/PolicyReader.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2020 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 com.android.car.power;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.frameworks.automotive.powerpolicy.CarPowerPolicy;
+import android.frameworks.automotive.powerpolicy.PowerComponent;
+import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReport;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.Xml;
+
+import com.android.car.CarServiceUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class to read and manage vendor power policies.
+ *
+ * <p>{@code CarPowerManagementService} manages power policies through {@code PolicyReader}. This
+ * class is not thread-safe, and must be used in the main thread or with additional serialization.
+ */
+final class PolicyReader {
+    private static final String TAG = PolicyReader.class.getSimpleName();
+    private static final String VENDOR_POLICY_PATH = "/vendor/etc/power_policy.xml";
+
+    private static final String NAMESPACE = null;
+    private static final Set<String> VALID_VERSIONS = new ArraySet<>(Arrays.asList("1.0"));
+    private static final String TAG_POWER_POLICY = "powerPolicy";
+    private static final String TAG_POLICY_GROUPS = "policyGroups";
+    private static final String TAG_POLICY_GROUP = "policyGroup";
+    private static final String TAG_DEFAULT_POLICY = "defaultPolicy";
+    private static final String TAG_NO_DEFAULT_POLICY = "noDefaultPolicy";
+    private static final String TAG_POLICIES = "policies";
+    private static final String TAG_POLICY = "policy";
+    private static final String TAG_OTHER_COMPONENTS = "otherComponents";
+    private static final String TAG_COMPONENT = "component";
+    private static final String TAG_SYSTEM_POLICY_OVERRIDES = "systemPolicyOverrides";
+    private static final String ATTR_VERSION = "version";
+    private static final String ATTR_ID = "id";
+    private static final String ATTR_STATE = "state";
+    private static final String ATTR_BEHAVIOR = "behavior";
+    private static final String POWER_ONOFF_ON = "on";
+    private static final String POWER_ONOFF_OFF = "off";
+    private static final String POWER_ONOFF_UNTOUCHED = "untouched";
+
+    private static final String POWER_COMPONENT_PREFIX = "POWER_COMPONENT_";
+    private static final int INVALID_POWER_COMPONENT = -1;
+    private static final int INVALID_POWER_STATE = -1;
+
+    private static final String POWER_COMPONENT_AUDIO = "AUDIO";
+    private static final String POWER_COMPONENT_MEDIA = "MEDIA";
+    private static final String POWER_COMPONENT_DISPLAY_MAIN = "DISPLAY_MAIN";
+    private static final String POWER_COMPONENT_DISPLAY_CLUSTER = "DISPLAY_CLUSTER";
+    private static final String POWER_COMPONENT_DISPLAY_FRONT_PASSENGER = "DISPLAY_FRONT_PASSENGER";
+    private static final String POWER_COMPONENT_DISPLAY_REAR_PASSENGER = "DISPLAY_REAR_PASSENGER";
+    private static final String POWER_COMPONENT_BLUETOOTH = "BLUETOOTH";
+    private static final String POWER_COMPONENT_WIFI = "WIFI";
+    private static final String POWER_COMPONENT_CELLULAR = "CELLULAR";
+    private static final String POWER_COMPONENT_ETHERNET = "ETHERNET";
+    private static final String POWER_COMPONENT_PROJECTION = "PROJECTION";
+    private static final String POWER_COMPONENT_NFC = "NFC";
+    private static final String POWER_COMPONENT_INPUT = "INPUT";
+    private static final String POWER_COMPONENT_VOICE_INTERACTION = "VOICE_INTERACTION";
+    private static final String POWER_COMPONENT_VISUAL_INTERACTION = "VISUAL_INTERACTION";
+    private static final String POWER_COMPONENT_TRUSTED_DEVICE_DETECTION =
+            "TRUSTED_DEVICE_DETECTION";
+
+    private static final String POWER_STATE_WAIT_FOR_VHAL = "WaitForVHAL";
+    private static final String POWER_STATE_ON = "On";
+    private static final String POWER_STATE_DEEP_SLEEP_ENTRY = "DeepSleepEntry";
+    private static final String POWER_STATE_SHUTDOWN_START = "ShutdownStart";
+
+    private static final String SYSTEM_POWER_POLICY_NO_USER_INTERACTION =
+            "system_power_policy_no_user_interaction";
+    private static final int[] SYSTEM_POLICY_ENABLED_COMPONENTS = {
+            PowerComponent.WIFI, PowerComponent.CELLULAR,
+            PowerComponent.ETHERNET, PowerComponent.TRUSTED_DEVICE_DETECTION
+    };
+    private static final int[] SYSTEM_POLICY_DISABLED_COMPONENTS = {
+            PowerComponent.AUDIO, PowerComponent.MEDIA, PowerComponent.DISPLAY_MAIN,
+            PowerComponent.DISPLAY_CLUSTER, PowerComponent.DISPLAY_FRONT_PASSENGER,
+            PowerComponent.DISPLAY_REAR_PASSENGER, PowerComponent.BLUETOOTH,
+            PowerComponent.PROJECTION, PowerComponent.NFC, PowerComponent.INPUT,
+            PowerComponent.VOICE_INTERACTION, PowerComponent.VISUAL_INTERACTION
+    };
+    private static final Set<Integer> SYSTEM_POLICY_CONFIGURABLE_COMPONENTS =
+            new ArraySet<>(Arrays.asList(PowerComponent.BLUETOOTH, PowerComponent.NFC,
+            PowerComponent.TRUSTED_DEVICE_DETECTION));
+
+    private ArrayMap<String, CarPowerPolicy> mRegisteredPowerPolicies;
+    private ArrayMap<String, SparseArray<String>> mPolicyGroups;
+    private CarPowerPolicy mSystemPowerPolicy;
+
+    /**
+     * Gets {@code CarPowerPolicy} corresponding to the given policy ID.
+     */
+    @Nullable
+    CarPowerPolicy getPowerPolicy(String policyId) {
+        return mRegisteredPowerPolicies.get(policyId);
+    }
+
+    /**
+     * Gets {@code CarPowerPolicy} corresponding to the given power state in the given power
+     * policy group.
+     */
+    @Nullable
+    CarPowerPolicy getDefaultPowerPolicyForState(String groupId, int state) {
+        SparseArray<String> group = mPolicyGroups.get(groupId);
+        if (group == null) {
+            return null;
+        }
+        String policyId = group.get(state);
+        if (policyId == null) {
+            return null;
+        }
+        return mRegisteredPowerPolicies.get(policyId);
+    }
+
+    /**
+     * Gets the system power policy.
+     *
+     * <p> At this moment, only one system power policy is supported.
+     */
+    @NonNull
+    CarPowerPolicy getSystemPowerPolicy() {
+        return mSystemPowerPolicy;
+    }
+
+    void init() {
+        mRegisteredPowerPolicies = new ArrayMap<>();
+        mPolicyGroups = new ArrayMap<>();
+        initSystemPowerPolicy();
+        readPowerPolicyConfiguration();
+    }
+
+    void dump(PrintWriter writer) {
+        String indent = "  ";
+        String doubleIndent = "    ";
+        writer.printf("Registered power policies:%s\n",
+                mRegisteredPowerPolicies.size() == 0 ? " none" : "");
+        for (Map.Entry<String, CarPowerPolicy> entry : mRegisteredPowerPolicies.entrySet()) {
+            writer.printf("%s%s\n", indent, toString(entry.getValue()));
+        }
+        writer.printf("Power policy groups:%s\n", mPolicyGroups.isEmpty() ? " none" : "");
+        for (Map.Entry<String, SparseArray<String>> entry : mPolicyGroups.entrySet()) {
+            writer.printf("%s%s\n", indent, entry.getKey());
+            SparseArray<String> group = entry.getValue();
+            for (int i = 0; i < group.size(); i++) {
+                writer.printf("%s- %s --> %s\n", doubleIndent, powerStateToString(group.keyAt(i)),
+                        group.valueAt(i));
+            }
+        }
+        writer.printf("System power policy: %s\n", toString(mSystemPowerPolicy));
+    }
+
+    private void readPowerPolicyConfiguration() {
+        try (InputStream inputStream = new FileInputStream(VENDOR_POLICY_PATH)) {
+            readPowerPolicyFromXml(inputStream);
+        } catch (IOException | XmlPullParserException | PolicyXmlException e) {
+            Slog.w(TAG, "Proceed without registered policies: failed to parse "
+                    + VENDOR_POLICY_PATH, e);
+        }
+    }
+
+    private void readPowerPolicyFromXml(InputStream stream) throws PolicyXmlException,
+            XmlPullParserException, IOException {
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null);
+        parser.setInput(stream, null);
+
+        // Ensure <powerPolicy> is the root
+        parser.nextTag();
+        parser.require(START_TAG, NAMESPACE, TAG_POWER_POLICY);
+        // Check version
+        String version = parser.getAttributeValue(NAMESPACE, ATTR_VERSION);
+        if (!VALID_VERSIONS.contains(version)) {
+            throw new PolicyXmlException("invalid XML version: " + version);
+        }
+
+        ArrayMap<String, CarPowerPolicy> registeredPolicies = new ArrayMap<>();
+        ArrayMap<String, SparseArray<String>> policyGroups = new ArrayMap<>();
+        CarPowerPolicy systemPolicyOverride = new CarPowerPolicy();
+
+        int type;
+        while ((type = parser.next()) != END_DOCUMENT && type != END_TAG) {
+            if (type != START_TAG) continue;
+            switch (parser.getName()) {
+                case TAG_POLICIES:
+                    registeredPolicies = parsePolicies(parser, true);
+                    break;
+                case TAG_POLICY_GROUPS:
+                    policyGroups = parsePolicyGroups(parser);
+                    break;
+                case TAG_SYSTEM_POLICY_OVERRIDES:
+                    systemPolicyOverride = parseSystemPolicyOverrides(parser);
+                    break;
+                default:
+                    throw new PolicyXmlException("unknown tag: " + parser.getName() + " under "
+                            + TAG_POWER_POLICY);
+            }
+        }
+        validatePolicyGroups(policyGroups, registeredPolicies);
+
+        mRegisteredPowerPolicies = registeredPolicies;
+        mPolicyGroups = policyGroups;
+        reconstructSystemPowerPolicy(systemPolicyOverride);
+    }
+
+    private ArrayMap<String, CarPowerPolicy> parsePolicies(XmlPullParser parser,
+            boolean includeOtherComponents) throws PolicyXmlException, XmlPullParserException,
+            IOException {
+        ArrayMap<String, CarPowerPolicy> policies = new ArrayMap<>();
+        int type;
+        while ((type = parser.next()) != END_DOCUMENT && type != END_TAG) {
+            if (type != START_TAG) continue;
+            if (TAG_POLICY.equals(parser.getName())) {
+                String policyId = parser.getAttributeValue(NAMESPACE, ATTR_ID);
+                if (policyId == null || policyId.equals("")) {
+                    throw new PolicyXmlException("no |" + ATTR_ID + "| attribute of |" + TAG_POLICY
+                            + "| tag");
+                }
+                policies.put(policyId, parsePolicy(parser, policyId, includeOtherComponents));
+            } else {
+                throw new PolicyXmlException("unknown tag: " + parser.getName() + " under "
+                        + TAG_POLICIES);
+            }
+        }
+        return policies;
+    }
+
+    private ArrayMap<String, SparseArray<String>> parsePolicyGroups(XmlPullParser parser) throws
+            PolicyXmlException, XmlPullParserException, IOException {
+        ArrayMap<String, SparseArray<String>> policyGroups = new ArrayMap<>();
+        int type;
+        while ((type = parser.next()) != END_DOCUMENT && type != END_TAG) {
+            if (type != START_TAG) continue;
+            if (TAG_POLICY_GROUP.equals(parser.getName())) {
+                String groupId = parser.getAttributeValue(NAMESPACE, ATTR_ID);
+                if (groupId == null || groupId.equals("")) {
+                    throw new PolicyXmlException("no |" + ATTR_ID + "| attribute of |"
+                            + TAG_POLICY_GROUP + "| tag");
+                }
+                policyGroups.put(groupId, parsePolicyGroup(parser));
+            } else {
+                throw new PolicyXmlException("unknown tag: " + parser.getName() + " under "
+                        + TAG_POLICY_GROUPS);
+            }
+        }
+        return policyGroups;
+    }
+
+    @Nullable
+    private CarPowerPolicy parseSystemPolicyOverrides(XmlPullParser parser) throws
+            PolicyXmlException, XmlPullParserException, IOException {
+        ArrayMap<String, CarPowerPolicy> systemOverrides = parsePolicies(parser, false);
+        if (systemOverrides.size() > 1) {
+            throw new PolicyXmlException("only one system power policy is supported");
+        }
+        CarPowerPolicy policyOverride =
+                systemOverrides.get(SYSTEM_POWER_POLICY_NO_USER_INTERACTION);
+        if (policyOverride == null) {
+            return null;
+        }
+        Set<Integer> visited = new ArraySet<>();
+        checkSystemPowerPolicyComponents(policyOverride.enabledComponents, visited);
+        checkSystemPowerPolicyComponents(policyOverride.disabledComponents, visited);
+        return policyOverride;
+    }
+
+    private CarPowerPolicy parsePolicy(XmlPullParser parser, String policyId,
+            boolean includeOtherComponents) throws PolicyXmlException, XmlPullParserException,
+            IOException {
+        CarPowerPolicy policy = new CarPowerPolicy();
+        policy.policyId = policyId;
+        SparseBooleanArray components = new SparseBooleanArray();
+        String behavior = POWER_ONOFF_UNTOUCHED;
+        boolean otherComponentsProcessed = false;
+        int type;
+        while ((type = parser.next()) != END_DOCUMENT && type != END_TAG) {
+            if (type != START_TAG) continue;
+            if (TAG_COMPONENT.equals(parser.getName())) {
+                String id = parser.getAttributeValue(NAMESPACE, ATTR_ID);
+                int powerComponent = toPowerComponent(id);
+                if (powerComponent == INVALID_POWER_COMPONENT) {
+                    throw new PolicyXmlException("invalid value(" + id + ") in |" + ATTR_ID
+                            + "| attribute of |" + TAG_COMPONENT + "| tag");
+                }
+                if (components.indexOfKey(powerComponent) >= 0) {
+                    throw new PolicyXmlException(id + " is specified more than once in |"
+                            + TAG_COMPONENT + "| tag");
+                }
+                String state = getText(parser);
+                switch (state) {
+                    case POWER_ONOFF_ON:
+                        components.put(powerComponent, true);
+                        break;
+                    case POWER_ONOFF_OFF:
+                        components.put(powerComponent, false);
+                        break;
+                    default:
+                        throw new PolicyXmlException("target state(" + state + ") for " + id
+                                + " is not valid");
+                }
+                skip(parser);
+            } else if (TAG_OTHER_COMPONENTS.equals(parser.getName())) {
+                if (!includeOtherComponents) {
+                    throw new PolicyXmlException("|" + TAG_OTHER_COMPONENTS
+                            + "| tag is not expected");
+                }
+                if (otherComponentsProcessed) {
+                    throw new PolicyXmlException("more than one |" + TAG_OTHER_COMPONENTS
+                            + "| tag");
+                }
+                otherComponentsProcessed = true;
+                behavior = parser.getAttributeValue(NAMESPACE, ATTR_BEHAVIOR);
+                if (behavior == null) {
+                    throw new PolicyXmlException("no |" + ATTR_BEHAVIOR + "| attribute of |"
+                            + TAG_OTHER_COMPONENTS + "| tag");
+                }
+                switch (behavior) {
+                    case POWER_ONOFF_ON:
+                    case POWER_ONOFF_OFF:
+                    case POWER_ONOFF_UNTOUCHED:
+                        break;
+                    default:
+                        throw new PolicyXmlException("invalid value(" + behavior + ") in |"
+                                + ATTR_BEHAVIOR + "| attribute of |" + TAG_OTHER_COMPONENTS
+                                + "| tag");
+                }
+                skip(parser);
+            } else {
+                throw new PolicyXmlException("unknown tag: " + parser.getName() + " under "
+                        + TAG_POLICY);
+            }
+        }
+        boolean enabled = false;
+        boolean untouched = false;
+        if (POWER_ONOFF_ON.equals(behavior)) {
+            enabled = true;
+        } else if (POWER_ONOFF_OFF.equals(behavior)) {
+            enabled = false;
+        } else {
+            untouched = true;
+        }
+        if (!untouched) {
+            for (int component = PowerComponent.AUDIO;
+                    component <= PowerComponent.TRUSTED_DEVICE_DETECTION; component++) {
+                if (components.indexOfKey(component) >= 0) continue;
+                components.put(component, enabled);
+            }
+        }
+        policy.enabledComponents = toIntArray(components, true);
+        policy.disabledComponents = toIntArray(components, false);
+        return policy;
+    }
+
+    private SparseArray<String> parsePolicyGroup(XmlPullParser parser) throws PolicyXmlException,
+            XmlPullParserException, IOException {
+        SparseArray<String> policyGroup = new SparseArray<>();
+        int type;
+        Set<Integer> visited = new ArraySet<>();
+        while ((type = parser.next()) != END_DOCUMENT && type != END_TAG) {
+            if (type != START_TAG) continue;
+            if (TAG_DEFAULT_POLICY.equals(parser.getName())) {
+                String id = parser.getAttributeValue(NAMESPACE, ATTR_ID);
+                if (id == null || id.isEmpty()) {
+                    throw new PolicyXmlException("no |" + ATTR_ID + "| attribute of |"
+                            + TAG_DEFAULT_POLICY + "| tag");
+                }
+                String state = parser.getAttributeValue(NAMESPACE, ATTR_STATE);
+                int powerState = toPowerState(state);
+                if (powerState == INVALID_POWER_STATE) {
+                    throw new PolicyXmlException("invalid value(" + state + ") in |" + ATTR_STATE
+                            + "| attribute of |" + TAG_DEFAULT_POLICY + "| tag");
+                }
+                if (visited.contains(powerState)) {
+                    throw new PolicyXmlException("power state(" + state
+                            + ") is specified more than once");
+                }
+                policyGroup.put(powerState, id);
+                visited.add(powerState);
+                skip(parser);
+            } else if (TAG_NO_DEFAULT_POLICY.equals(parser.getName())) {
+                String state = parser.getAttributeValue(NAMESPACE, ATTR_STATE);
+                int powerState = toPowerState(state);
+                if (powerState == INVALID_POWER_STATE) {
+                    throw new PolicyXmlException("invalid value(" + state + ") in |" + ATTR_STATE
+                            + "| attribute of |" + TAG_DEFAULT_POLICY + "| tag");
+                }
+                if (visited.contains(powerState)) {
+                    throw new PolicyXmlException("power state(" + state
+                            + ") is specified more than once");
+                }
+                visited.add(powerState);
+                skip(parser);
+            } else {
+                throw new PolicyXmlException("unknown tag: " + parser.getName() + " under "
+                        + TAG_POLICY_GROUP);
+            }
+        }
+        return policyGroup;
+    }
+
+    private void validatePolicyGroups(ArrayMap<String, SparseArray<String>> policyGroups,
+            ArrayMap<String, CarPowerPolicy> registeredPolicies) throws PolicyXmlException {
+        for (Map.Entry<String, SparseArray<String>> entry : policyGroups.entrySet()) {
+            SparseArray<String> group = entry.getValue();
+            for (int i = 0; i < group.size(); i++) {
+                String policyId = group.valueAt(i);
+                if (!registeredPolicies.containsKey(group.valueAt(i))) {
+                    throw new PolicyXmlException("group(id: " + entry.getKey()
+                            + ") contains invalid policy(id: " + policyId + ")");
+                }
+            }
+        }
+    }
+
+    private void reconstructSystemPowerPolicy(@Nullable CarPowerPolicy policyOverride) {
+        if (policyOverride == null) return;
+
+        List<Integer> enabledComponents = Arrays.stream(SYSTEM_POLICY_ENABLED_COMPONENTS).boxed()
+                .collect(Collectors.toList());
+        List<Integer> disabledComponents = Arrays.stream(SYSTEM_POLICY_DISABLED_COMPONENTS).boxed()
+                .collect(Collectors.toList());
+        for (int i = 0; i < policyOverride.enabledComponents.length; i++) {
+            removeComponent(disabledComponents, policyOverride.enabledComponents[i]);
+            addComponent(enabledComponents, policyOverride.enabledComponents[i]);
+        }
+        for (int i = 0; i < policyOverride.disabledComponents.length; i++) {
+            removeComponent(enabledComponents, policyOverride.disabledComponents[i]);
+            addComponent(disabledComponents, policyOverride.disabledComponents[i]);
+        }
+        mSystemPowerPolicy.enabledComponents = CarServiceUtils.toIntArray(enabledComponents);
+        mSystemPowerPolicy.disabledComponents = CarServiceUtils.toIntArray(disabledComponents);
+    }
+
+    private void removeComponent(List<Integer> components, int component) {
+        int index = components.lastIndexOf(component);
+        if (index != -1) {
+            components.remove(index);
+        }
+    }
+
+    private void addComponent(List<Integer> components, int component) {
+        int index = components.lastIndexOf(component);
+        if (index == -1) {
+            components.add(component);
+        }
+    }
+
+    private void initSystemPowerPolicy() {
+        mSystemPowerPolicy = new CarPowerPolicy();
+        mSystemPowerPolicy.policyId = SYSTEM_POWER_POLICY_NO_USER_INTERACTION;
+        mSystemPowerPolicy.enabledComponents = SYSTEM_POLICY_ENABLED_COMPONENTS.clone();
+        mSystemPowerPolicy.disabledComponents = SYSTEM_POLICY_DISABLED_COMPONENTS.clone();
+    }
+
+    private String getText(XmlPullParser parser) throws PolicyXmlException, XmlPullParserException,
+            IOException {
+        if (parser.getEventType() != START_TAG) {
+            throw new PolicyXmlException("tag pair doesn't match");
+        }
+        parser.next();
+        if (parser.getEventType() != TEXT) {
+            throw new PolicyXmlException("tag value is not found");
+        }
+        return parser.getText();
+    }
+
+    private void skip(XmlPullParser parser) throws PolicyXmlException, XmlPullParserException,
+            IOException {
+        int type = parser.getEventType();
+        if (type != START_TAG && type != TEXT) {
+            throw new PolicyXmlException("tag pair doesn't match");
+        }
+        int depth = 1;
+        while (depth != 0) {
+            switch (parser.next()) {
+                case END_TAG:
+                    depth--;
+                    break;
+                case START_TAG:
+                    depth++;
+                    break;
+            }
+        }
+    }
+
+    void checkSystemPowerPolicyComponents(int[] components, Set<Integer> visited) throws
+            PolicyXmlException {
+        for (int i = 0; i < components.length; i++) {
+            int component = components[i];
+            if (!isOverridableComponent(component)) {
+                throw new PolicyXmlException("Power component(" + powerComponentToString(component)
+                        + ") cannot be overridden");
+            }
+            if (visited.contains(component)) {
+                throw new PolicyXmlException("Power component(" + powerComponentToString(component)
+                        + ") is specified more than once");
+            }
+            visited.add(component);
+        }
+    }
+
+    boolean isOverridableComponent(int component) {
+        return SYSTEM_POLICY_CONFIGURABLE_COMPONENTS.contains(component);
+    }
+
+    private int toPowerComponent(String component) {
+        if (component == null || !component.startsWith(POWER_COMPONENT_PREFIX)) {
+            return INVALID_POWER_COMPONENT;
+        }
+        component = component.substring(POWER_COMPONENT_PREFIX.length());
+        switch (component) {
+            case POWER_COMPONENT_AUDIO:
+                return PowerComponent.AUDIO;
+            case POWER_COMPONENT_MEDIA:
+                return PowerComponent.MEDIA;
+            case POWER_COMPONENT_DISPLAY_MAIN:
+                return PowerComponent.DISPLAY_MAIN;
+            case POWER_COMPONENT_DISPLAY_CLUSTER:
+                return PowerComponent.DISPLAY_CLUSTER;
+            case POWER_COMPONENT_DISPLAY_FRONT_PASSENGER:
+                return PowerComponent.DISPLAY_FRONT_PASSENGER;
+            case POWER_COMPONENT_DISPLAY_REAR_PASSENGER:
+                return PowerComponent.DISPLAY_REAR_PASSENGER;
+            case POWER_COMPONENT_BLUETOOTH:
+                return PowerComponent.BLUETOOTH;
+            case POWER_COMPONENT_WIFI:
+                return PowerComponent.WIFI;
+            case POWER_COMPONENT_CELLULAR:
+                return PowerComponent.CELLULAR;
+            case POWER_COMPONENT_ETHERNET:
+                return PowerComponent.ETHERNET;
+            case POWER_COMPONENT_PROJECTION:
+                return PowerComponent.PROJECTION;
+            case POWER_COMPONENT_NFC:
+                return PowerComponent.NFC;
+            case POWER_COMPONENT_INPUT:
+                return PowerComponent.INPUT;
+            case POWER_COMPONENT_VOICE_INTERACTION:
+                return PowerComponent.VOICE_INTERACTION;
+            case POWER_COMPONENT_VISUAL_INTERACTION:
+                return PowerComponent.VISUAL_INTERACTION;
+            case POWER_COMPONENT_TRUSTED_DEVICE_DETECTION:
+                return PowerComponent.TRUSTED_DEVICE_DETECTION;
+            default:
+                return INVALID_POWER_COMPONENT;
+        }
+    }
+
+    private int toPowerState(String state) {
+        if (state == null) {
+            return INVALID_POWER_STATE;
+        }
+        switch (state) {
+            case POWER_STATE_WAIT_FOR_VHAL:
+                return VehicleApPowerStateReport.WAIT_FOR_VHAL;
+            case POWER_STATE_ON:
+                return VehicleApPowerStateReport.ON;
+            case POWER_STATE_DEEP_SLEEP_ENTRY:
+                return VehicleApPowerStateReport.DEEP_SLEEP_ENTRY;
+            case POWER_STATE_SHUTDOWN_START:
+                return VehicleApPowerStateReport.SHUTDOWN_START;
+            default:
+                return INVALID_POWER_STATE;
+        }
+    }
+
+    private String powerStateToString(int state) {
+        switch (state) {
+            case VehicleApPowerStateReport.WAIT_FOR_VHAL:
+                return POWER_STATE_WAIT_FOR_VHAL;
+            case VehicleApPowerStateReport.ON:
+                return POWER_STATE_ON;
+            case VehicleApPowerStateReport.DEEP_SLEEP_ENTRY:
+                return POWER_STATE_DEEP_SLEEP_ENTRY;
+            case VehicleApPowerStateReport.SHUTDOWN_START:
+                return POWER_STATE_SHUTDOWN_START;
+            default:
+                return "unknown power state";
+        }
+    }
+
+    private String powerComponentToString(int component) {
+        switch (component) {
+            case PowerComponent.AUDIO:
+                return POWER_COMPONENT_AUDIO;
+            case PowerComponent.MEDIA:
+                return POWER_COMPONENT_MEDIA;
+            case PowerComponent.DISPLAY_MAIN:
+                return POWER_COMPONENT_DISPLAY_MAIN;
+            case PowerComponent.DISPLAY_CLUSTER:
+                return POWER_COMPONENT_DISPLAY_CLUSTER;
+            case PowerComponent.DISPLAY_FRONT_PASSENGER:
+                return POWER_COMPONENT_DISPLAY_FRONT_PASSENGER;
+            case PowerComponent.DISPLAY_REAR_PASSENGER:
+                return POWER_COMPONENT_DISPLAY_REAR_PASSENGER;
+            case PowerComponent.BLUETOOTH:
+                return POWER_COMPONENT_BLUETOOTH;
+            case PowerComponent.WIFI:
+                return POWER_COMPONENT_WIFI;
+            case PowerComponent.CELLULAR:
+                return POWER_COMPONENT_CELLULAR;
+            case PowerComponent.ETHERNET:
+                return POWER_COMPONENT_ETHERNET;
+            case PowerComponent.PROJECTION:
+                return POWER_COMPONENT_PROJECTION;
+            case PowerComponent.NFC:
+                return POWER_COMPONENT_NFC;
+            case PowerComponent.INPUT:
+                return POWER_COMPONENT_INPUT;
+            case PowerComponent.VOICE_INTERACTION:
+                return POWER_COMPONENT_VOICE_INTERACTION;
+            case PowerComponent.VISUAL_INTERACTION:
+                return POWER_COMPONENT_VISUAL_INTERACTION;
+            case PowerComponent.TRUSTED_DEVICE_DETECTION:
+                return POWER_COMPONENT_TRUSTED_DEVICE_DETECTION;
+            default:
+                return "unknown component";
+        }
+    }
+
+    private String toString(CarPowerPolicy policy) {
+        return policy.policyId + "(enabledComponents: "
+                + componentsToString(policy.enabledComponents) + " | disabledComponents: "
+                + componentsToString(policy.disabledComponents) + ")";
+    }
+
+    private String componentsToString(int[] components) {
+        StringBuffer buffer = new StringBuffer();
+        for (int i = 0; i < components.length; i++) {
+            if (i > 0) buffer.append(", ");
+            buffer.append(powerComponentToString(components[i]));
+        }
+        return buffer.toString();
+    }
+
+    private static int[] toIntArray(SparseBooleanArray array, boolean value) {
+        int arraySize = array.size();
+        int returnSize = 0;
+        for (int i = 0; i < arraySize; i++) {
+            if (array.valueAt(i) == value) returnSize++;
+        }
+        int[] ret = new int[returnSize];
+        for (int i = 0; i < returnSize; i++) {
+            ret[i] = array.keyAt(i);
+        }
+        return ret;
+    }
+
+    private static class PolicyXmlException extends Exception {
+        PolicyXmlException(String message) {
+            super(message);
+        }
+    }
+}