blob: 793758c16ca5bee4c620447324f52a5594b65c19 [file] [log] [blame]
/*
* 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.isValidPowerComponent;
import static android.car.hardware.power.PowerComponentUtil.powerComponentToString;
import static android.car.hardware.power.PowerComponentUtil.toPowerComponent;
import android.annotation.Nullable;
import android.car.hardware.power.CarPowerPolicy;
import android.car.hardware.power.PowerComponent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.car.CarLog;
import com.android.car.systeminterface.SystemInterface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractionManagerService;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
/**
* Class that manages power components in the system. A power component mediator corresponding to a
* power component is created and registered to this class. A power component mediator encapsulates
* the function of powering on/off.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class PowerComponentHandler {
private static final String TAG = CarLog.tagFor(PowerComponentHandler.class);
private static final String FORCED_OFF_COMPONENTS_FILENAME =
"forced_off_components";
private final Context mContext;
private final SystemInterface mSystemInterface;
private final AtomicFile mComponentStateFile;
private final SparseArray<PowerComponentMediator> mPowerComponentMediators =
new SparseArray<>();
PowerComponentHandler(Context context, SystemInterface systemInterface) {
mContext = context;
mSystemInterface = systemInterface;
mComponentStateFile = new AtomicFile(new File(systemInterface.getSystemCarDir(),
FORCED_OFF_COMPONENTS_FILENAME));
}
void init() {
PowerComponentMediatorFactory factory = new PowerComponentMediatorFactory();
for (int component = FIRST_POWER_COMPONENT;
component <= LAST_POWER_COMPONENT; component++) {
PowerComponentMediator mediator = factory.createPowerComponent(component);
String componentName = powerComponentToString(component);
if (mediator == null) {
Slog.w(TAG, "Power component(" + componentName + ") is not valid or doesn't need a "
+ "mediator");
continue;
}
if (!mediator.isComponentAvailable()) {
Slog.w(TAG, "Power component(" + componentName + ") is not available");
continue;
}
mPowerComponentMediators.put(component, mediator);
}
}
void applyPowerPolicy(CarPowerPolicy policy) {
SparseBooleanArray forcedOffComponents = readComponentState();
boolean componentModified = false;
int[] enabledComponents = policy.getEnabledComponents();
int[] disabledComponents = policy.getDisabledComponents();
for (int i = 0; i < enabledComponents.length; i++) {
componentModified |= setComponentEnabledInternal(enabledComponents[i], true,
forcedOffComponents);
}
for (int i = 0; i < disabledComponents.length; i++) {
componentModified |= setComponentEnabledInternal(disabledComponents[i], false,
forcedOffComponents);
}
if (componentModified) {
writeComponentState(forcedOffComponents);
}
}
void setComponentEnabled(int component, boolean enabled) throws PowerComponentException {
if (!isValidPowerComponent(component)) {
throw new PowerComponentException("invalid power component");
}
SparseBooleanArray forcedOffComponents = readComponentState();
if (setComponentEnabledInternal(component, enabled, forcedOffComponents)) {
writeComponentState(forcedOffComponents);
}
}
private boolean setComponentEnabledInternal(int component, boolean enabled,
SparseBooleanArray forcedOffComponents) {
PowerComponentMediator mediator = mPowerComponentMediators.get(component);
if (mediator == null) {
Slog.w(TAG, powerComponentToString(component) + " doesn't have a mediator");
return false;
}
boolean componentModified = false;
boolean isEnabled = mediator.isEnabled();
if (enabled) {
if (forcedOffComponents.get(component)) {
forcedOffComponents.delete(component);
componentModified = true;
} else {
// The last state set by user is off. So, we don't power on the component.
return false;
}
} else {
if (!forcedOffComponents.get(component)) {
forcedOffComponents.put(component, true);
componentModified = true;
}
}
if (enabled != isEnabled) {
mediator.setEnabled(enabled);
}
return componentModified;
}
private SparseBooleanArray readComponentState() {
SparseBooleanArray forcedOffComponents = new SparseBooleanArray();
boolean invalid = false;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(mComponentStateFile.openRead(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
int component = toPowerComponent(line.trim(), false);
if (component == INVALID_POWER_COMPONENT) {
invalid = true;
break;
}
forcedOffComponents.put(component, true);
}
} catch (FileNotFoundException e) {
// Behave as if there are no forced-off components.
return new SparseBooleanArray();
} catch (IOException e) {
Slog.w(TAG, "Failed to read " + FORCED_OFF_COMPONENTS_FILENAME + ": " + e);
return new SparseBooleanArray();
}
if (invalid) {
mComponentStateFile.delete();
return new SparseBooleanArray();
}
return forcedOffComponents;
}
private void writeComponentState(SparseBooleanArray forcedOffComponents) {
FileOutputStream fos;
try {
fos = mComponentStateFile.startWrite();
} catch (IOException e) {
Slog.e(TAG, "Cannot create " + FORCED_OFF_COMPONENTS_FILENAME, e);
return;
}
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
for (int i = 0; i < forcedOffComponents.size(); i++) {
writer.write(powerComponentToString(forcedOffComponents.keyAt(i)));
writer.newLine();
}
writer.flush();
mComponentStateFile.finishWrite(fos);
} catch (IOException e) {
mComponentStateFile.failWrite(fos);
Slog.e(TAG, "Writing " + FORCED_OFF_COMPONENTS_FILENAME + " failed", e);
}
}
abstract class PowerComponentMediator {
protected int mComponentId;
PowerComponentMediator(int component) {
mComponentId = component;
}
public void setEnabled(boolean enabled) {}
public boolean isEnabled() {
return false;
}
public boolean isComponentAvailable() {
return false;
}
}
// TODO(b/178824607): Check if power policy can turn on/off display as quickly as the existing
// implementation.
private final class DisplayPowerComponentMediator extends PowerComponentMediator {
DisplayPowerComponentMediator() {
super(PowerComponent.DISPLAY);
}
@Override
public boolean isComponentAvailable() {
// It is assumed that display is supported in all vehicles.
return true;
}
@Override
public void setEnabled(boolean enabled) {
mSystemInterface.setDisplayState(enabled);
}
@Override
public boolean isEnabled() {
return mSystemInterface.isDisplayEnabled();
}
}
private final class WifiPowerComponentMediator extends PowerComponentMediator {
private final WifiManager mWifiManager;
WifiPowerComponentMediator() {
super(PowerComponent.WIFI);
mWifiManager = mContext.getSystemService(WifiManager.class);
}
@Override
public boolean isComponentAvailable() {
PackageManager pm = mContext.getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_WIFI);
}
@Override
public void setEnabled(boolean enabled) {
mWifiManager.setWifiEnabled(enabled);
}
@Override
public boolean isEnabled() {
return mWifiManager.isWifiEnabled();
}
}
private final class VoiceInteractionPowerComponentMediator extends PowerComponentMediator {
private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
private boolean mIsEnabled = true;
VoiceInteractionPowerComponentMediator() {
super(PowerComponent.VOICE_INTERACTION);
mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
}
@Override
public boolean isComponentAvailable() {
return mVoiceInteractionManagerService != null;
}
@Override
public void setEnabled(boolean enabled) {
try {
mVoiceInteractionManagerService.setDisabled(!enabled);
mIsEnabled = enabled;
} catch (RemoteException e) {
Slog.w(TAG, "IVoiceInteractionManagerService.setDisabled(" + !enabled + ") failed",
e);
}
}
@Override
public boolean isEnabled() {
// IVoiceInteractionManagerService doesn't have a method to tell enabled state. Assuming
// voice interaction is controlled only by AAOS CPMS, it tracks the state internally.
// TODO(b/178504489): Add isEnabled to IVoiceInterctionManagerService and use it.
return mIsEnabled;
}
}
private final class PowerComponentMediatorFactory {
@Nullable
PowerComponentMediator createPowerComponent(int component) {
switch (component) {
case PowerComponent.AUDIO:
// We don't control audio in framework level, because audio is enabled or
// disabled in audio HAL according to the current power policy.
return null;
case PowerComponent.MEDIA:
return null;
case PowerComponent.DISPLAY:
return new DisplayPowerComponentMediator();
case PowerComponent.WIFI:
return new WifiPowerComponentMediator();
case PowerComponent.CELLULAR:
return null;
case PowerComponent.ETHERNET:
return null;
case PowerComponent.PROJECTION:
return null;
case PowerComponent.NFC:
return null;
case PowerComponent.INPUT:
return null;
case PowerComponent.VOICE_INTERACTION:
return new VoiceInteractionPowerComponentMediator();
case PowerComponent.VISUAL_INTERACTION:
return null;
case PowerComponent.TRUSTED_DEVICE_DETECTION:
return null;
case PowerComponent.MICROPHONE:
// We don't control microphone in framework level, because microphone is enabled
// or disabled in audio HAL according to the current power policy.
return null;
case PowerComponent.BLUETOOTH:
// com.android.car.BluetoothDeviceConnectionPolicy handles power state change.
// So, bluetooth mediator is not created.
return null;
case PowerComponent.LOCATION:
// GNSS HAL handles power state change. So, location mediator is not created.
return null;
case PowerComponent.CPU:
return null;
default:
Slog.w(TAG, "Unknown component(" + component + ")");
return null;
}
}
}
static class PowerComponentException extends Exception {
PowerComponentException(String message) {
super(message);
}
}
}