blob: bb78810c519c3a97889ef87aca2a902dd3805f45 [file] [log] [blame]
/*
* 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 com.android.car.power;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.FileObserver;
import android.os.SystemProperties;
import android.util.IndentingPrintWriter;
import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Objects;
/**
* Class to handle Silent Mode and Non-Silent Mode.
*
* <p>This monitors {@code /sys/power/pm_silentmode_hw_state} to figure out when to switch to Silent
* Mode and updates {@code /sys/power/pm_silentmode_kernel_state} to tell early-init services about
* Silent Mode change. Also, it handles forced Silent Mode for testing purpose, which is given
* through reboot reason.
*/
final class SilentModeHandler {
static final String SILENT_MODE_FORCED_SILENT = "forced-silent";
static final String SILENT_MODE_FORCED_NON_SILENT = "forced-non-silent";
static final String SILENT_MODE_NON_FORCED = "non-forced-silent-mode";
private static final String TAG = CarLog.tagFor(SilentModeHandler.class);
private static final String SYSFS_FILENAME_HW_STATE_MONITORING =
"/sys/power/pm_silentmode_hw_state";
private static final String SYSFS_FILENAME_KERNEL_SILENTMODE =
"/sys/power/pm_silentmode_kernel_state";
private static final String VALUE_SILENT_MODE = "1";
private static final String VALUE_NON_SILENT_MODE = "0";
private static final String SYSTEM_BOOT_REASON = "sys.boot.reason";
private static final String FORCED_NON_SILENT = "reboot,forcednonsilent";
private static final String FORCED_SILENT = "reboot,forcedsilent";
private final Object mLock = new Object();
private final CarPowerManagementService mService;
private final String mHwStateMonitoringFileName;
private final String mKernelSilentModeFileName;
@GuardedBy("mLock")
private FileObserver mFileObserver;
@GuardedBy("mLock")
private boolean mSilentModeByHwState;
@GuardedBy("mLock")
private boolean mForcedMode;
@VisibleForTesting
SilentModeHandler(@NonNull CarPowerManagementService service,
@Nullable String hwStateMonitoringFileName, @Nullable String kernelSilentModeFileName,
@Nullable String bootReason) {
Objects.requireNonNull(service, "CarPowerManagementService must not be null");
mService = service;
mHwStateMonitoringFileName = hwStateMonitoringFileName == null
? SYSFS_FILENAME_HW_STATE_MONITORING
: hwStateMonitoringFileName;
mKernelSilentModeFileName = kernelSilentModeFileName == null
? SYSFS_FILENAME_KERNEL_SILENTMODE
: kernelSilentModeFileName;
if (bootReason == null) {
bootReason = SystemProperties.get(SYSTEM_BOOT_REASON);
}
switch (bootReason) {
case FORCED_SILENT:
Slogf.i(TAG, "Starting in forced silent mode");
mForcedMode = true;
mSilentModeByHwState = true;
break;
case FORCED_NON_SILENT:
Slogf.i(TAG, "Starting in forced non-silent mode");
mForcedMode = true;
mSilentModeByHwState = false;
break;
default:
mForcedMode = false;
}
}
void init() {
boolean forcedMode;
boolean silentMode;
synchronized (mLock) {
forcedMode = mForcedMode;
silentMode = mSilentModeByHwState;
}
if (forcedMode) {
updateKernelSilentMode(silentMode);
mService.notifySilentModeChange(silentMode);
Slogf.i(TAG, "Now in forced mode: monitoring %s is disabled",
mHwStateMonitoringFileName);
} else {
startMonitoringSilentModeHwState();
}
}
void release() {
synchronized (mLock) {
stopMonitoringSilentModeHwStateLocked();
}
}
void dump(IndentingPrintWriter writer) {
synchronized (mLock) {
writer.printf("Monitoring HW state signal: %b\n", mFileObserver != null);
writer.printf("Silent mode by HW state signal: %b\n", mSilentModeByHwState);
writer.printf("Forced silent mode: %b\n", mForcedMode);
}
}
boolean isSilentMode() {
synchronized (mLock) {
return mSilentModeByHwState;
}
}
void querySilentModeHwState() {
FileObserver fileObserver;
synchronized (mLock) {
fileObserver = mFileObserver;
}
if (fileObserver != null) {
fileObserver.onEvent(FileObserver.MODIFY, mHwStateMonitoringFileName);
}
}
void updateKernelSilentMode(boolean silent) {
try (BufferedWriter writer =
new BufferedWriter(new FileWriter(mKernelSilentModeFileName))) {
String value = silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE;
writer.write(value);
writer.flush();
Slogf.i(TAG, "%s is updated to %s", mKernelSilentModeFileName, value);
} catch (IOException e) {
Slogf.w(TAG, "Failed to update %s to %s", mKernelSilentModeFileName,
silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE);
}
}
void setSilentMode(String silentMode) {
switch (silentMode) {
case SILENT_MODE_FORCED_SILENT:
switchToForcedMode(true);
break;
case SILENT_MODE_FORCED_NON_SILENT:
switchToForcedMode(false);
break;
case SILENT_MODE_NON_FORCED:
switchToNonForcedMode();
break;
default:
Slogf.w(TAG, "Unsupported silent mode: %s", silentMode);
}
}
private void switchToForcedMode(boolean silentMode) {
boolean updated = false;
synchronized (mLock) {
if (!mForcedMode) {
stopMonitoringSilentModeHwStateLocked();
mForcedMode = true;
}
if (mSilentModeByHwState != silentMode) {
mSilentModeByHwState = silentMode;
updated = true;
}
}
if (updated) {
updateKernelSilentMode(silentMode);
mService.notifySilentModeChange(silentMode);
}
Slogf.i(TAG, "Now in forced %s mode: monitoring %s is disabled",
silentMode ? "silent" : "non-silent", mHwStateMonitoringFileName);
}
private void switchToNonForcedMode() {
boolean updated = false;
synchronized (mLock) {
if (mForcedMode) {
Slogf.i(TAG, "Now in non forced mode: monitoring %s is started",
mHwStateMonitoringFileName);
mForcedMode = false;
updated = true;
}
}
if (updated) {
startMonitoringSilentModeHwState();
}
}
private void startMonitoringSilentModeHwState() {
File monitorFile = new File(mHwStateMonitoringFileName);
if (!monitorFile.exists()) {
Slogf.w(TAG, "Failed to start monitoring Silent Mode HW state: %s doesn't exist",
mHwStateMonitoringFileName);
return;
}
FileObserver fileObserver = new FileObserver(monitorFile, FileObserver.MODIFY) {
@Override
public void onEvent(int event, String filename) {
boolean newSilentMode;
boolean oldSilentMode;
synchronized (mLock) {
// FileObserver can report events even after stopWatching is called. To ignore
// such events, check the current internal state.
if (mForcedMode) {
return;
}
oldSilentMode = mSilentModeByHwState;
try {
String contents = IoUtils.readFileAsString(mHwStateMonitoringFileName)
.trim();
mSilentModeByHwState = VALUE_SILENT_MODE.equals(contents);
Slogf.i(TAG, "%s indicates %s mode", mHwStateMonitoringFileName,
mSilentModeByHwState ? "silent" : "non-silent");
} catch (Exception e) {
Slogf.w(TAG, e, "Failed to read %s", mHwStateMonitoringFileName);
return;
}
newSilentMode = mSilentModeByHwState;
}
if (newSilentMode != oldSilentMode) {
updateKernelSilentMode(newSilentMode);
mService.notifySilentModeChange(newSilentMode);
}
}
};
synchronized (mLock) {
mFileObserver = fileObserver;
}
fileObserver.startWatching();
// Trigger the observer to get the initial contents
querySilentModeHwState();
}
private void stopMonitoringSilentModeHwStateLocked() {
if (mFileObserver != null) {
mFileObserver.stopWatching();
mFileObserver = null;
}
}
}