/* * 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 android.car.hardware.power.PowerComponentUtil.FIRST_POWER_COMPONENT; import static android.car.hardware.power.PowerComponentUtil.INVALID_POWER_COMPONENT; import static android.car.hardware.power.PowerComponentUtil.LAST_POWER_COMPONENT; import static android.car.hardware.power.PowerComponentUtil.powerComponentToString; import static android.car.hardware.power.PowerComponentUtil.toPowerComponent; import static android.frameworks.automotive.powerpolicy.PowerComponent.MINIMUM_CUSTOM_COMPONENT_VALUE; import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 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.Nullable; import android.car.builtin.util.Slogf; import android.car.feature.FeatureFlags; import android.car.hardware.power.CarPowerPolicy; import android.car.hardware.power.PowerComponent; import android.hardware.automotive.vehicle.VehicleApPowerStateReport; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.car.CarLog; import com.android.car.CarServiceUtils; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.car.internal.util.IndentingPrintWriter; import com.android.car.internal.util.Lists; import com.android.car.power.CarPowerDumpProto.PolicyReaderProto; import com.android.car.power.CarPowerDumpProto.PolicyReaderProto.ComponentNameToValue; import com.android.car.power.CarPowerDumpProto.PolicyReaderProto.IdToPolicyGroup; import com.android.car.power.CarPowerDumpProto.PolicyReaderProto.IdToPolicyGroup.PolicyGroup; import com.android.car.power.CarPowerDumpProto.PolicyReaderProto.IdToPolicyGroup.PolicyGroup.StateToDefaultPolicy; import com.android.car.power.CarPowerDumpProto.PolicyReaderProto.PowerPolicy; import com.android.internal.annotations.VisibleForTesting; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; /** * Helper class to read and manage vendor power policies. * *

{@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. */ public final class PolicyReader { public static final String POWER_STATE_WAIT_FOR_VHAL = "WaitForVHAL"; public static final String POWER_STATE_ON = "On"; // TODO(b/286303350): Remove all system power policy definitions after refactor is complete static final String SYSTEM_POWER_POLICY_PREFIX = "system_power_policy_"; // Preemptive system power policy used for disabling user interaction in Silent Mode or Garage // Mode. static final String POWER_POLICY_ID_NO_USER_INTERACTION = SYSTEM_POWER_POLICY_PREFIX + "no_user_interaction"; // Preemptive system power policy used for preparing Suspend-to-RAM. static final String POWER_POLICY_ID_SUSPEND_PREP = SYSTEM_POWER_POLICY_PREFIX + "suspend_prep"; // Non-preemptive system power policy used for turning all components on. static final String POWER_POLICY_ID_ALL_ON = SYSTEM_POWER_POLICY_PREFIX + "all_on"; // Non-preemptive system power policy used to represent minimal on state. static final String POWER_POLICY_ID_INITIAL_ON = SYSTEM_POWER_POLICY_PREFIX + "initial_on"; static final int INVALID_POWER_STATE = -1; private static final String TAG = CarLog.tagFor(PolicyReader.class); private static final String VENDOR_POLICY_PATH = "/vendor/etc/automotive/power_policy.xml"; private static final String NAMESPACE = null; private static final Set 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 TAG_CUSTOM_COMPONENTS = "customComponents"; private static final String TAG_CUSTOM_COMPONENT = "customComponent"; private static final String ATTR_DEFAULT_POLICY_GROUP = "defaultPolicyGroup"; 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 int[] ALL_COMPONENTS; private static final int[] NO_COMPONENTS = EMPTY_INT_ARRAY; private static final int[] INITIAL_ON_COMPONENTS = { PowerComponent.AUDIO, PowerComponent.DISPLAY, PowerComponent.CPU }; private static final int[] NO_USER_INTERACTION_ENABLED_COMPONENTS = { PowerComponent.WIFI, PowerComponent.CELLULAR, PowerComponent.ETHERNET, PowerComponent.TRUSTED_DEVICE_DETECTION, PowerComponent.CPU }; private static final int[] NO_USER_INTERACTION_DISABLED_COMPONENTS = { PowerComponent.AUDIO, PowerComponent.MEDIA, PowerComponent.DISPLAY, PowerComponent.BLUETOOTH, PowerComponent.PROJECTION, PowerComponent.NFC, PowerComponent.INPUT, PowerComponent.VOICE_INTERACTION, PowerComponent.VISUAL_INTERACTION, PowerComponent.LOCATION, PowerComponent.MICROPHONE }; private static final Set SYSTEM_POLICY_CONFIGURABLE_COMPONENTS = new ArraySet<>(Arrays.asList(PowerComponent.BLUETOOTH, PowerComponent.NFC, PowerComponent.TRUSTED_DEVICE_DETECTION)); private static final int[] SUSPEND_PREP_DISABLED_COMPONENTS = { PowerComponent.AUDIO, PowerComponent.BLUETOOTH, PowerComponent.WIFI, PowerComponent.LOCATION, PowerComponent.MICROPHONE, PowerComponent.CPU }; private static final CarPowerPolicy POWER_POLICY_ALL_ON; private static final CarPowerPolicy POWER_POLICY_INITIAL_ON; private static final CarPowerPolicy POWER_POLICY_SUSPEND_PREP; static { int allCount = LAST_POWER_COMPONENT - FIRST_POWER_COMPONENT + 1; ALL_COMPONENTS = new int[allCount]; int[] initialOnDisabledComponents = new int[allCount - INITIAL_ON_COMPONENTS.length]; int pos = 0; for (int c = FIRST_POWER_COMPONENT; c <= LAST_POWER_COMPONENT; c++) { ALL_COMPONENTS[c - FIRST_POWER_COMPONENT] = c; if (!containsComponent(INITIAL_ON_COMPONENTS, c)) { initialOnDisabledComponents[pos++] = c; } } POWER_POLICY_ALL_ON = new CarPowerPolicy(POWER_POLICY_ID_ALL_ON, ALL_COMPONENTS.clone(), NO_COMPONENTS.clone()); POWER_POLICY_INITIAL_ON = new CarPowerPolicy(POWER_POLICY_ID_INITIAL_ON, INITIAL_ON_COMPONENTS.clone(), initialOnDisabledComponents); POWER_POLICY_SUSPEND_PREP = new CarPowerPolicy(POWER_POLICY_ID_SUSPEND_PREP, NO_COMPONENTS.clone(), SUSPEND_PREP_DISABLED_COMPONENTS.clone()); } // Allows for injecting mock feature flag values during testing private FeatureFlags mFeatureFlags; private ArrayMap mRegisteredPowerPolicies; private ArrayMap> mPolicyGroups; // TODO(b/286303350): remove once power policy refactor complete private ArrayMap mPreemptivePowerPolicies; // TODO(b/286303350): remove once power policy refactor complete private String mDefaultPolicyGroupId; private ArrayMap mCustomComponents = new ArrayMap<>(); /** * 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 group = mPolicyGroups.get( (groupId == null || groupId.isEmpty()) ? mDefaultPolicyGroupId : groupId); if (group == null) { return null; } String policyId = group.get(state); if (policyId == null) { return null; } return mRegisteredPowerPolicies.get(policyId); } /** * Gets the preemptive power policy corresponding to the given policy ID. * *

When a preemptive power policy is the current power policy, applying a regular power * policy is deferred until the preemptive power policy is released. */ @Nullable CarPowerPolicy getPreemptivePowerPolicy(String policyId) { return mPreemptivePowerPolicies.get(policyId); } boolean isPowerPolicyGroupAvailable(String groupId) { return mPolicyGroups.containsKey(groupId); } boolean isPreemptivePowerPolicy(String policyId) { return mPreemptivePowerPolicies.containsKey(policyId); } /** * Gets default power policy group ID. * * @return {@code String} containing power policy group ID or {@code null} if it is not defined */ @Nullable String getDefaultPowerPolicyGroup() { return mDefaultPolicyGroupId; } void init(FeatureFlags fakeFeatureFlags) { mFeatureFlags = fakeFeatureFlags; Slogf.d(TAG, "PolicyReader is initializing, carPowerPolicyRefactoring = " + mFeatureFlags.carPowerPolicyRefactoring()); initPolicies(); if (!mFeatureFlags.carPowerPolicyRefactoring()) { readPowerPolicyConfiguration(); } } /** * Creates and registers a new power policy. * * @return {@code PolicyOperationStatus.OK}, if successful. Otherwise, the other values. */ @PolicyOperationStatus.ErrorCode int definePowerPolicy(String policyId, String[] enabledComponents, String[] disabledComponents) { // policyId cannot be empty or null if (policyId == null || policyId.length() == 0) { int error = PolicyOperationStatus.ERROR_INVALID_POWER_POLICY_ID; Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, "policyId cannot be empty")); return error; } if (!mFeatureFlags.carPowerPolicyRefactoring()) { if (isSystemPowerPolicy(policyId)) { int error = PolicyOperationStatus.ERROR_INVALID_POWER_POLICY_ID; Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, "policyId should not start with " + SYSTEM_POWER_POLICY_PREFIX)); return error; } } if (mRegisteredPowerPolicies.containsKey(policyId)) { int error = PolicyOperationStatus.ERROR_DOUBLE_REGISTERED_POWER_POLICY_ID; Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, policyId)); return error; } SparseBooleanArray components = new SparseBooleanArray(); int status = parseComponents(enabledComponents, true, components); if (status != PolicyOperationStatus.OK) { return status; } status = parseComponents(disabledComponents, false, components); if (status != PolicyOperationStatus.OK) { return status; } CarPowerPolicy policy = new CarPowerPolicy(policyId, toIntArray(components, true), toIntArray(components, false)); mRegisteredPowerPolicies.put(policyId, policy); return PolicyOperationStatus.OK; } /** * Defines and registers a new power policy group. * * @return {@code PolicyOperationStatus.OK}, if successful. Otherwise, the other values. */ @PolicyOperationStatus.ErrorCode int definePowerPolicyGroup(String policyGroupId, SparseArray defaultPolicyPerState) { if (policyGroupId == null) { return PolicyOperationStatus.ERROR_INVALID_POWER_POLICY_GROUP_ID; } if (mPolicyGroups.containsKey(policyGroupId)) { int error = PolicyOperationStatus.ERROR_DOUBLE_REGISTERED_POWER_POLICY_GROUP_ID; Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, policyGroupId)); return error; } for (int i = 0; i < defaultPolicyPerState.size(); i++) { int state = defaultPolicyPerState.keyAt(i); String policyId = defaultPolicyPerState.valueAt(i); if (!mRegisteredPowerPolicies.containsKey(policyId)) { int error = PolicyOperationStatus.ERROR_NOT_REGISTERED_POWER_POLICY_ID; Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, policyId + " for " + vhalPowerStateToString(state))); return error; } } mPolicyGroups.put(policyGroupId, defaultPolicyPerState); return PolicyOperationStatus.OK; } @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) void dump(IndentingPrintWriter writer) { int size = mCustomComponents.size(); writer.printf("Registered custom components:"); if (size == 0) { writer.printf(" none\n"); } else { writer.printf("\n"); writer.increaseIndent(); for (int i = 0; i < size; i++) { Object key = mCustomComponents.keyAt(i); writer.printf("Component name: %s, value: %s\n", key, mCustomComponents.get(key)); } writer.decreaseIndent(); } size = mRegisteredPowerPolicies.size(); writer.printf("Registered power policies:"); if (size == 0) { writer.printf(" none\n"); } else { writer.printf("\n"); writer.increaseIndent(); for (int i = 0; i < size; i++) { writer.println(mRegisteredPowerPolicies.valueAt(i).toString()); } writer.decreaseIndent(); } size = mPolicyGroups.size(); writer.printf("Power policy groups:"); if (size == 0) { writer.printf(" none\n"); } else { writer.printf("\n"); writer.increaseIndent(); for (int i = 0; i < size; i++) { String key = mPolicyGroups.keyAt(i); writer.println(key); writer.increaseIndent(); SparseArray group = mPolicyGroups.get(key); for (int j = 0; j < group.size(); j++) { writer.printf("- %s --> %s\n", vhalPowerStateToString(group.keyAt(j)), group.valueAt(j)); } writer.decreaseIndent(); } writer.decreaseIndent(); } writer.println("Preemptive power policy:"); writer.increaseIndent(); if (mFeatureFlags.carPowerPolicyRefactoring()) { writer.println("Preemptive power policies not supported w/refactored power policy"); } else { for (int i = 0; i < mPreemptivePowerPolicies.size(); i++) { writer.println(mPreemptivePowerPolicies.valueAt(i).toString()); } } writer.decreaseIndent(); } @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) void dumpProtoPowerPolicies( ProtoOutputStream proto, long fieldNumber, ArrayMap policies) { for (int i = 0; i < policies.size(); i++) { long policiesToken = proto.start(fieldNumber); CarPowerPolicy powerPolicy = policies.valueAt(i); proto.write(PowerPolicy.POLICY_ID, powerPolicy.getPolicyId()); int[] enabledComponents = powerPolicy.getEnabledComponents(); for (int j = 0; j < enabledComponents.length; j++) { proto.write(PowerPolicy.ENABLED_COMPONENTS, powerComponentToString(enabledComponents[j])); } int[] disabledComponents = powerPolicy.getDisabledComponents(); for (int j = 0; j < disabledComponents.length; j++) { proto.write(PowerPolicy.DISABLED_COMPONENTS, powerComponentToString(disabledComponents[j])); } proto.end(policiesToken); } } @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) void dumpProto(ProtoOutputStream proto) { long policyReaderToken = proto.start(CarPowerDumpProto.POLICY_READER); for (int i = 0; i < mCustomComponents.size(); i++) { long customComponentMappingsToken = proto.start( PolicyReaderProto.CUSTOM_COMPONENT_MAPPINGS); Object key = mCustomComponents.keyAt(i); proto.write(ComponentNameToValue.COMPONENT_NAME, key.toString()); proto.write(ComponentNameToValue.COMPONENT_VALUE, mCustomComponents.get(key).intValue()); proto.end(customComponentMappingsToken); } dumpProtoPowerPolicies( proto, PolicyReaderProto.REGISTERED_POWER_POLICIES, mRegisteredPowerPolicies); for (int i = 0; i < mPolicyGroups.size(); i++) { long powerPolicyGroupMappingsToken = proto.start( PolicyReaderProto.POWER_POLICY_GROUP_MAPPINGS); String policyGroupId = mPolicyGroups.keyAt(i); proto.write(IdToPolicyGroup.POLICY_GROUP_ID, policyGroupId); SparseArray group = mPolicyGroups.get(policyGroupId); long policyGroupMappingsToken = proto.start(IdToPolicyGroup.POLICY_GROUP); for (int j = 0; j < group.size(); j++) { long defaultPolicyMappingsToken = proto.start(PolicyGroup.DEFAULT_POLICY_MAPPINGS); proto.write(StateToDefaultPolicy.STATE, vhalPowerStateToString(group.keyAt(j))); proto.write(StateToDefaultPolicy.DEFAULT_POLICY_ID, group.valueAt(j)); proto.end(defaultPolicyMappingsToken); } proto.end(policyGroupMappingsToken); proto.end(powerPolicyGroupMappingsToken); } if (!mFeatureFlags.carPowerPolicyRefactoring()) { dumpProtoPowerPolicies( proto, PolicyReaderProto.PREEMPTIVE_POWER_POLICIES, mPreemptivePowerPolicies); } proto.end(policyReaderToken); } @VisibleForTesting void initPolicies() { mRegisteredPowerPolicies = new ArrayMap<>(); mPolicyGroups = new ArrayMap<>(); if (!mFeatureFlags.carPowerPolicyRefactoring()) { registerBasicPowerPolicies(); mPreemptivePowerPolicies = new ArrayMap<>(); mPreemptivePowerPolicies.put(POWER_POLICY_ID_NO_USER_INTERACTION, new CarPowerPolicy(POWER_POLICY_ID_NO_USER_INTERACTION, NO_USER_INTERACTION_ENABLED_COMPONENTS.clone(), NO_USER_INTERACTION_DISABLED_COMPONENTS.clone())); mPreemptivePowerPolicies.put(POWER_POLICY_ID_SUSPEND_PREP, POWER_POLICY_SUSPEND_PREP); } } private void readPowerPolicyConfiguration() { try (InputStream inputStream = new FileInputStream(VENDOR_POLICY_PATH)) { readPowerPolicyFromXml(inputStream); } catch (IOException | XmlPullParserException | PolicyXmlException e) { Slogf.w(TAG, "Proceed without registered policies: failed to parse %s: %s", VENDOR_POLICY_PATH, e); } } @VisibleForTesting 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 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 registeredPolicies; List intermediateCarPowerPolicies = new ArrayList<>(); ArrayMap> policyGroups = new ArrayMap<>(); List intermediateSystemPolicyOverride = new ArrayList<>(); ArrayMap customComponents = new ArrayMap<>(); CarPowerPolicy systemPolicyOverride; String defaultGroupPolicyId = null; int type; while ((type = parser.next()) != END_DOCUMENT && type != END_TAG) { if (type != START_TAG) continue; switch (parser.getName()) { case TAG_POLICIES: intermediateCarPowerPolicies.addAll(parsePolicies(parser, true)); break; case TAG_POLICY_GROUPS: defaultGroupPolicyId = parser.getAttributeValue(NAMESPACE, ATTR_DEFAULT_POLICY_GROUP); policyGroups = parsePolicyGroups(parser); break; case TAG_SYSTEM_POLICY_OVERRIDES: intermediateSystemPolicyOverride.addAll(parseSystemPolicyOverrides(parser)); break; case TAG_CUSTOM_COMPONENTS: customComponents = parseCustomComponents(parser); break; default: throw new PolicyXmlException("unknown tag: " + parser.getName() + " under " + TAG_POWER_POLICY); } } registeredPolicies = validatePowerPolicies(intermediateCarPowerPolicies, customComponents); systemPolicyOverride = validateSystemOverrides(intermediateSystemPolicyOverride, customComponents); validatePolicyGroups(policyGroups, registeredPolicies, defaultGroupPolicyId); mCustomComponents = customComponents; mDefaultPolicyGroupId = defaultGroupPolicyId; mRegisteredPowerPolicies = registeredPolicies; registerBasicPowerPolicies(); mPolicyGroups = policyGroups; reconstructSystemPowerPolicy(systemPolicyOverride); } private ArrayMap parseCustomComponents(XmlPullParser parser) throws XmlPullParserException, IOException, PolicyXmlException { ArrayMap customComponentsMap = new ArrayMap<>(); int type; while ((type = parser.next()) != END_DOCUMENT && type != END_TAG) { if (type != START_TAG) continue; if (TAG_CUSTOM_COMPONENT.equals(parser.getName())) { int componentValue = Integer.parseInt(parser.getAttributeValue(NAMESPACE, "value")); String componentName = getText(parser); customComponentsMap.put(componentName, componentValue); if (componentValue < MINIMUM_CUSTOM_COMPONENT_VALUE) { throw new PolicyXmlException( "Invalid custom component value " + componentValue + " " + componentName); } skip(parser); } else { throw new PolicyXmlException( "unknown tag: " + parser.getName() + " under " + TAG_POLICIES); } } return customComponentsMap; } private List parsePolicies(XmlPullParser parser, boolean includeOtherComponents) throws PolicyXmlException, XmlPullParserException, IOException { List policies = new ArrayList<>(); 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"); } if (includeOtherComponents && isSystemPowerPolicy(policyId)) { throw new PolicyXmlException("Policy ID should not start with " + SYSTEM_POWER_POLICY_PREFIX); } policies.add(parsePolicy(parser, policyId, includeOtherComponents)); } else { throw new PolicyXmlException("unknown tag: " + parser.getName() + " under " + TAG_POLICIES); } } return policies; } private ArrayMap> parsePolicyGroups(XmlPullParser parser) throws PolicyXmlException, XmlPullParserException, IOException { ArrayMap> 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; } private List parseSystemPolicyOverrides(XmlPullParser parser) throws PolicyXmlException, XmlPullParserException, IOException { return parsePolicies(/* parser= */ parser, /* includeOtherComponents= */ false); } @Nullable private CarPowerPolicy validateSystemOverrides( List systemPolicyOverrideIntermediate, ArrayMap customComponents) throws PolicyXmlException { int numOverrides = systemPolicyOverrideIntermediate.size(); if (numOverrides == 0) { return null; } if (numOverrides > 1) { throw new PolicyXmlException("only one system power policy is supported: " + numOverrides + " system policies exist"); } if (!systemPolicyOverrideIntermediate.get(0).policyId.equals( POWER_POLICY_ID_NO_USER_INTERACTION)) { throw new PolicyXmlException("system power policy id should be " + POWER_POLICY_ID_NO_USER_INTERACTION); } CarPowerPolicy policyOverride = toCarPowerPolicy(systemPolicyOverrideIntermediate.get(0), customComponents); Set visited = new ArraySet<>(); checkSystemPowerPolicyComponents(policyOverride.getEnabledComponents(), visited); checkSystemPowerPolicyComponents(policyOverride.getDisabledComponents(), visited); return policyOverride; } private IntermediateCarPowerPolicy parsePolicy(XmlPullParser parser, String policyId, boolean includeOtherComponents) throws PolicyXmlException, IOException, XmlPullParserException { ArrayMap components = new ArrayMap<>(); 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 powerComponent = parser.getAttributeValue(NAMESPACE, ATTR_ID); 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 " + powerComponent + " 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); } } return new IntermediateCarPowerPolicy(policyId, components, behavior); } private CarPowerPolicy toCarPowerPolicy(IntermediateCarPowerPolicy intermediatePolicy, ArrayMap customComponents) throws PolicyXmlException { SparseBooleanArray components = new SparseBooleanArray(); // Convert string values of IntermediateCarPowerPolicy to a CarPowerPolicy ArrayMap intermediatePolicyComponents = intermediatePolicy.components; for (int i = 0; i < intermediatePolicyComponents.size(); i++) { String componentId = intermediatePolicyComponents.keyAt(i); int powerComponent = toPowerComponent(componentId, true); if (powerComponent == INVALID_POWER_COMPONENT) { powerComponent = toCustomPowerComponentId(componentId, customComponents); } if (powerComponent == INVALID_POWER_COMPONENT) { throw new PolicyXmlException(" Unknown component id : " + componentId); } if (components.indexOfKey(powerComponent) >= 0) { throw new PolicyXmlException( "invalid value(" + componentId + ") in |" + ATTR_ID + "| attribute of |" + TAG_COMPONENT + "| tag"); } components.put(powerComponent, intermediatePolicyComponents.valueAt(i)); } boolean enabled; boolean untouched = false; if (POWER_ONOFF_ON.equals(intermediatePolicy.otherBehavior)) { enabled = true; } else if (POWER_ONOFF_OFF.equals(intermediatePolicy.otherBehavior)) { enabled = false; } else { enabled = false; untouched = true; } if (!untouched) { for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT; component++) { if (components.indexOfKey(component) >= 0) continue; components.put(component, enabled); } for (int i = 0; i < customComponents.size(); ++i) { int componentId = customComponents.valueAt(i); if (components.indexOfKey(componentId) < 0) { // key not found components.put(componentId, enabled); } } } return new CarPowerPolicy(intermediatePolicy.policyId, toIntArray(components, true), toIntArray(components, false)); } private int toCustomPowerComponentId(String id, ArrayMap customComponents) { return customComponents.getOrDefault(id, INVALID_POWER_COMPONENT); } private SparseArray parsePolicyGroup(XmlPullParser parser) throws PolicyXmlException, XmlPullParserException, IOException { SparseArray policyGroup = new SparseArray<>(); int type; Set 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 ArrayMap validatePowerPolicies( List intermediateCarPowerPolicies, ArrayMap customComponents) throws PolicyXmlException { ArrayMap powerPolicies = new ArrayMap<>(); for (int index = 0; index < intermediateCarPowerPolicies.size(); ++index) { IntermediateCarPowerPolicy intermediateCarPowerPolicy = intermediateCarPowerPolicies.get(index); powerPolicies.put(intermediateCarPowerPolicy.policyId, toCarPowerPolicy(intermediateCarPowerPolicy, customComponents)); } return powerPolicies; } private void validatePolicyGroups(ArrayMap> policyGroups, ArrayMap registeredPolicies, String defaultGroupPolicyId) throws PolicyXmlException { for (Map.Entry> entry : policyGroups.entrySet()) { SparseArray 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 + ")"); } } } if ((defaultGroupPolicyId == null || defaultGroupPolicyId.isEmpty()) && !policyGroups.isEmpty()) { Slogf.w(TAG, "No defaultGroupPolicyId is defined"); } if (defaultGroupPolicyId != null && !policyGroups.containsKey(defaultGroupPolicyId)) { throw new PolicyXmlException( "defaultGroupPolicyId is defined, but group with this ID doesn't exist "); } } private void reconstructSystemPowerPolicy(@Nullable CarPowerPolicy policyOverride) { if (policyOverride == null) return; List enabledComponents = CarServiceUtils.asList( NO_USER_INTERACTION_ENABLED_COMPONENTS); List disabledComponents = CarServiceUtils.asList( NO_USER_INTERACTION_DISABLED_COMPONENTS); int[] overrideEnabledComponents = policyOverride.getEnabledComponents(); int[] overrideDisabledComponents = policyOverride.getDisabledComponents(); for (int i = 0; i < overrideEnabledComponents.length; i++) { removeComponent(disabledComponents, overrideEnabledComponents[i]); addComponent(enabledComponents, overrideEnabledComponents[i]); } for (int i = 0; i < overrideDisabledComponents.length; i++) { removeComponent(enabledComponents, overrideDisabledComponents[i]); addComponent(disabledComponents, overrideDisabledComponents[i]); } mPreemptivePowerPolicies.put(POWER_POLICY_ID_NO_USER_INTERACTION, new CarPowerPolicy(POWER_POLICY_ID_NO_USER_INTERACTION, CarServiceUtils.toIntArray(enabledComponents), CarServiceUtils.toIntArray(disabledComponents))); } private void registerBasicPowerPolicies() { mRegisteredPowerPolicies.put(POWER_POLICY_ID_ALL_ON, POWER_POLICY_ALL_ON); mRegisteredPowerPolicies.put(POWER_POLICY_ID_INITIAL_ON, POWER_POLICY_INITIAL_ON); } private void removeComponent(List components, int component) { int index = components.lastIndexOf(component); if (index != -1) { components.remove(index); } } private void addComponent(List components, int component) { int index = components.lastIndexOf(component); if (index == -1) { components.add(component); } } 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; default: break; } } } void checkSystemPowerPolicyComponents(int[] components, Set 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 component >= MINIMUM_CUSTOM_COMPONENT_VALUE // custom components are overridable || SYSTEM_POLICY_CONFIGURABLE_COMPONENTS.contains(component); } @PolicyOperationStatus.ErrorCode int parseComponents(String[] componentArr, boolean enabled, SparseBooleanArray components) { ArrayList customComponentIds = new ArrayList<>(); for (int i = 0; i < componentArr.length; i++) { int component = toPowerComponent(componentArr[i], false); if (component == INVALID_POWER_COMPONENT) { try { component = Integer.parseInt(componentArr[i]); } catch (NumberFormatException e) { Slogf.e(TAG, "Error parsing component ID " + e.toString()); return PolicyOperationStatus.ERROR_INVALID_POWER_COMPONENT; } if (component < MINIMUM_CUSTOM_COMPONENT_VALUE) { int error = PolicyOperationStatus.ERROR_INVALID_POWER_COMPONENT; Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, componentArr[i])); return error; } } if (components.indexOfKey(component) >= 0) { int error = PolicyOperationStatus.ERROR_DUPLICATED_POWER_COMPONENT; Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, componentArr[i])); return error; } components.put(component, enabled); customComponentIds.add(component); } for (int i = 0; i < customComponentIds.size(); ++i) { int componentId = customComponentIds.get(i); // Add only new components if (!mCustomComponents.containsValue(componentId)) { mCustomComponents.put(String.valueOf(componentId), componentId); } } return PolicyOperationStatus.OK; } static 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; default: return INVALID_POWER_STATE; } } static String vhalPowerStateToString(int state) { switch (state) { case VehicleApPowerStateReport.WAIT_FOR_VHAL: return POWER_STATE_WAIT_FOR_VHAL; case VehicleApPowerStateReport.ON: return POWER_STATE_ON; default: return "unknown power state"; } } static boolean isSystemPowerPolicy(String policyId) { return policyId == null ? false : policyId.startsWith(SYSTEM_POWER_POLICY_PREFIX); } 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]; int count = 0; for (int i = 0; i < arraySize; i++) { if (array.valueAt(i) == value) { ret[count++] = array.keyAt(i); } } return ret; } private static boolean containsComponent(int[] arr, int component) { for (int element : arr) { if (element == component) return true; } return false; } ArrayMap getCustomComponents() { return mCustomComponents; } @VisibleForTesting Set getAllComponents() { Set allComponents = new ArraySet<>(Lists.asImmutableList(ALL_COMPONENTS)); allComponents.addAll(mCustomComponents.values()); return allComponents; } @VisibleForTesting static final class PolicyXmlException extends Exception { PolicyXmlException(String message) { super(message); } } private static final class IntermediateCarPowerPolicy { public final String policyId; public final ArrayMap components; public final String otherBehavior; IntermediateCarPowerPolicy(String policyId, ArrayMap components, String behavior) { this.policyId = policyId; this.components = components; this.otherBehavior = behavior; } } }