blob: 9c26dc93d7182b37c210fc9e6d313e80ab201027 [file] [log] [blame]
/*
* Copyright (C) 2015 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.app.ActivityManager;
import android.car.Car;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.hardware.power.CarPowerPolicy;
import android.car.hardware.power.CarPowerPolicyFilter;
import android.car.hardware.power.ICarPower;
import android.car.hardware.power.ICarPowerPolicyListener;
import android.car.hardware.power.ICarPowerStateListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.frameworks.automotive.powerpolicy.internal.ICarPowerPolicySystemNotification;
import android.frameworks.automotive.powerpolicy.internal.PolicyState;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReport;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateShutdownParam;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import com.android.car.CarLocalServices;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.CarServiceUtils;
import com.android.car.CarStatsLogHelper;
import com.android.car.ICarImpl;
import com.android.car.R;
import com.android.car.am.ContinuousBlankActivity;
import com.android.car.hal.PowerHalService;
import com.android.car.hal.PowerHalService.PowerState;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.user.CarUserNoticeService;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.utils.Slogf;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
/**
* Power Management service class for cars. Controls the power states and interacts with other
* parts of the system to ensure its own state.
*/
public class CarPowerManagementService extends ICarPower.Stub implements
CarServiceBase, PowerHalService.PowerEventListener {
public static final String SILENT_MODE_FORCED_SILENT =
SilentModeHandler.SILENT_MODE_FORCED_SILENT;
public static final String SILENT_MODE_FORCED_NON_SILENT =
SilentModeHandler.SILENT_MODE_FORCED_NON_SILENT;
public static final String SILENT_MODE_NON_FORCED = SilentModeHandler.SILENT_MODE_NON_FORCED;
private static final String TAG = CarLog.tagFor(CarPowerManagementService.class);
private static final String WIFI_STATE_FILENAME = "wifi_state";
private static final String WIFI_STATE_MODIFIED = "forcibly_disabled";
private static final String WIFI_STATE_ORIGINAL = "original";
// If Suspend to RAM fails, we retry with an exponential back-off:
// The wait interval will be 10 msec, 20 msec, 40 msec, ...
// Once the wait interval goes beyond 100 msec, it is fixed at 100 msec.
private static final long INITIAL_SUSPEND_RETRY_INTERVAL_MS = 10;
private static final long MAX_RETRY_INTERVAL_MS = 100;
// Minimum and maximum wait duration before the system goes into Suspend to RAM.
private static final long MIN_SUSPEND_WAIT_DURATION_MS = 0;
private static final long MAX_SUSPEND_WAIT_DURATION_MS = 3 * 60 * 1000;
private static final long CAR_POWER_POLICY_DAEMON_FIND_MARGINAL_TIME_MS = 300;
private static final long CAR_POWER_POLICY_DAEMON_BIND_RETRY_INTERVAL_MS = 500;
private static final int CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY = 3;
private static final String CAR_POWER_POLICY_DAEMON_INTERFACE =
"carpowerpolicy_system_notification";
// TODO: Make this OEM configurable.
private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
private static final int SHUTDOWN_EXTEND_MAX_MS = 5000;
// maxGarageModeRunningDurationInSecs should be equal or greater than this. 15 min for now.
private static final int MIN_MAX_GARAGE_MODE_DURATION_MS = 15 * 60 * 1000;
// in secs
private static final String PROP_MAX_GARAGE_MODE_DURATION_OVERRIDE =
"android.car.garagemodeduration";
private final Object mLock = new Object();
private final Object mSimulationWaitObject = new Object();
private final Context mContext;
private final PowerHalService mHal;
private final SystemInterface mSystemInterface;
// The listeners that complete simply by returning from onStateChanged()
private final PowerManagerCallbackList<ICarPowerStateListener> mPowerManagerListeners =
new PowerManagerCallbackList<>(
l -> CarPowerManagementService.this.doUnregisterListener(l));
// The listeners that must indicate asynchronous completion by calling finished().
private final PowerManagerCallbackList<ICarPowerStateListener>
mPowerManagerListenersWithCompletion = new PowerManagerCallbackList<>(
l -> CarPowerManagementService.this.doUnregisterListener(l));
@GuardedBy("mLock")
private final Set<IBinder> mListenersWeAreWaitingFor = new HashSet<>();
@GuardedBy("mLock")
private final LinkedList<CpmsState> mPendingPowerStates = new LinkedList<>();
private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
getClass().getSimpleName());
private final PowerHandler mHandler = new PowerHandler(mHandlerThread.getLooper(), this);
private final UserManager mUserManager;
private final CarUserService mUserService;
private final WifiManager mWifiManager;
private final AtomicFile mWifiStateFile;
private final boolean mWifiAdjustmentForSuspend;
// This is a temp work-around to reduce user switching delay after wake-up.
private final boolean mSwitchGuestUserBeforeSleep;
// CPMS tries to enter Suspend to RAM within the duration specified at
// mMaxSuspendWaitDurationMs. The default max duration is MAX_SUSPEND_WAIT_DRATION, and can be
// overridden by setting config_maxSuspendWaitDuration in an overrlay resource.
// The valid range is MIN_SUSPEND_WAIT_DRATION to MAX_SUSPEND_WAIT_DURATION.
private final long mMaxSuspendWaitDurationMs;
@GuardedBy("mSimulationWaitObject")
private boolean mWakeFromSimulatedSleep;
@GuardedBy("mSimulationWaitObject")
private boolean mInSimulatedDeepSleepMode;
@GuardedBy("mLock")
private CpmsState mCurrentState;
@GuardedBy("mLock")
private Timer mTimer;
@GuardedBy("mLock")
private long mProcessingStartTime;
@GuardedBy("mLock")
private long mLastSleepEntryTime;
@GuardedBy("mLock")
private boolean mTimerActive;
@GuardedBy("mLock")
private int mNextWakeupSec;
@GuardedBy("mLock")
private boolean mShutdownOnFinish;
@GuardedBy("mLock")
private boolean mShutdownOnNextSuspend;
@GuardedBy("mLock")
private boolean mIsBooting = true;
@GuardedBy("mLock")
private int mShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
@GuardedBy("mLock")
private int mShutdownPollingIntervalMs = SHUTDOWN_POLLING_INTERVAL_MS;
@GuardedBy("mLock")
private boolean mRebootAfterGarageMode;
@GuardedBy("mLock")
private boolean mGarageModeShouldExitImmediately;
@GuardedBy("mLock")
private ICarPowerPolicySystemNotification mCarPowerPolicyDaemon;
@GuardedBy("mLock")
private boolean mConnectionInProgress;
private BinderHandler mBinderHandler;
@GuardedBy("mLock")
private String mCurrentPowerPolicyId;
@GuardedBy("mLock")
private String mPendingPowerPolicyId;
@GuardedBy("mLock")
private String mCurrentPowerPolicyGroupId;
@GuardedBy("mLock")
private boolean mIsPowerPolicyLocked;
@GuardedBy("mLock")
private boolean mHasControlOverDaemon;
@GuardedBy("mLock")
@Nullable
private IResultReceiver mFactoryResetCallback;
private final PowerManagerCallbackList<ICarPowerPolicyListener> mPowerPolicyListeners =
new PowerManagerCallbackList<>(
l -> CarPowerManagementService.this.mPowerPolicyListeners.unregister(l));
private final PowerComponentHandler mPowerComponentHandler;
private final PolicyReader mPolicyReader = new PolicyReader();
private final SilentModeHandler mSilentModeHandler;
interface ActionOnDeath<T extends IInterface> {
void take(T listener);
}
private final class PowerManagerCallbackList<T extends IInterface> extends
RemoteCallbackList<T> {
private ActionOnDeath<T> mActionOnDeath;
PowerManagerCallbackList(ActionOnDeath<T> action) {
mActionOnDeath = action;
}
/**
* Old version of {@link #onCallbackDied(E, Object)} that
* does not provide a cookie.
*/
@Override
public void onCallbackDied(T listener) {
Slogf.i(TAG, "binderDied %s", listener.asBinder());
mActionOnDeath.take(listener);
}
}
public CarPowerManagementService(Context context, PowerHalService powerHal,
SystemInterface systemInterface, CarUserService carUserService,
ICarPowerPolicySystemNotification powerPolicyDaemon) {
this(context, context.getResources(), powerHal, systemInterface, UserManager.get(context),
carUserService, powerPolicyDaemon,
new PowerComponentHandler(context, systemInterface),
/* silentModeHwStatePath= */ null, /* silentModeKernelStatePath= */ null,
/* bootReason= */ null);
}
@VisibleForTesting
public CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
SystemInterface systemInterface, UserManager userManager, CarUserService carUserService,
ICarPowerPolicySystemNotification powerPolicyDaemon,
PowerComponentHandler powerComponentHandler, @Nullable String silentModeHwStatePath,
@Nullable String silentModeKernelStatePath, @Nullable String bootReason) {
mContext = context;
mHal = powerHal;
mSystemInterface = systemInterface;
mUserManager = userManager;
mShutdownPrepareTimeMs = resources.getInteger(
R.integer.maxGarageModeRunningDurationInSecs) * 1000;
mSwitchGuestUserBeforeSleep = resources.getBoolean(
R.bool.config_switchGuestUserBeforeGoingSleep);
if (mShutdownPrepareTimeMs < MIN_MAX_GARAGE_MODE_DURATION_MS) {
Slogf.w(TAG,
"maxGarageModeRunningDurationInSecs smaller than minimum required, "
+ "resource:%d(ms) while should exceed:%d(ms), Ignore resource.",
mShutdownPrepareTimeMs, MIN_MAX_GARAGE_MODE_DURATION_MS);
mShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
}
mUserService = carUserService;
mCarPowerPolicyDaemon = powerPolicyDaemon;
if (powerPolicyDaemon != null) {
// For testing purpose
mHasControlOverDaemon = true;
}
mWifiManager = context.getSystemService(WifiManager.class);
mWifiStateFile = new AtomicFile(
new File(mSystemInterface.getSystemCarDir(), WIFI_STATE_FILENAME));
mWifiAdjustmentForSuspend = getWifiAdjustmentForSuspendConfig();
mPowerComponentHandler = powerComponentHandler;
mSilentModeHandler = new SilentModeHandler(this, silentModeHwStatePath,
silentModeKernelStatePath, bootReason);
mMaxSuspendWaitDurationMs = Math.max(MIN_SUSPEND_WAIT_DURATION_MS,
Math.min(getMaxSuspendWaitDurationConfig(), MAX_SUSPEND_WAIT_DURATION_MS));
}
/**
* Overrides timers to keep testing time short.
*
* <p>Passing in {@code 0} resets the value to the default.
*/
@VisibleForTesting
public void setShutdownTimersForTest(int pollingIntervalMs, int shutdownTimeoutMs) {
synchronized (mLock) {
mShutdownPollingIntervalMs =
(pollingIntervalMs == 0) ? SHUTDOWN_POLLING_INTERVAL_MS : pollingIntervalMs;
mShutdownPrepareTimeMs =
(shutdownTimeoutMs == 0) ? SHUTDOWN_EXTEND_MAX_MS : shutdownTimeoutMs;
}
}
@VisibleForTesting
protected HandlerThread getHandlerThread() {
return mHandlerThread;
}
@Override
public void init() {
mPolicyReader.init();
mPowerComponentHandler.init();
mHal.setListener(this);
if (mHal.isPowerStateSupported()) {
// Initialize CPMS in WAIT_FOR_VHAL state
onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, CarPowerStateListener.WAIT_FOR_VHAL);
} else {
Slogf.w(TAG, "Vehicle hal does not support power state yet.");
onApPowerStateChange(CpmsState.ON, CarPowerStateListener.ON);
}
mSystemInterface.startDisplayStateMonitoring(this);
connectToPowerPolicyDaemon();
}
@Override
public void release() {
if (mBinderHandler != null) {
mBinderHandler.unlinkToDeath();
}
synchronized (mLock) {
releaseTimerLocked();
mCurrentState = null;
mCarPowerPolicyDaemon = null;
mHandler.cancelAll();
mListenersWeAreWaitingFor.clear();
}
mSystemInterface.stopDisplayStateMonitoring();
mPowerManagerListeners.kill();
mPowerPolicyListeners.kill();
mSystemInterface.releaseAllWakeLocks();
}
@Override
public void dump(IndentingPrintWriter writer) {
synchronized (mLock) {
writer.println("*PowerManagementService*");
writer.printf("mCurrentState: %s\n", mCurrentState);
writer.printf("mProcessingStartTime: %d\n", mProcessingStartTime);
writer.printf("mLastSleepEntryTime: %d\n", mLastSleepEntryTime);
writer.printf("mNextWakeupSec: %d\n", mNextWakeupSec);
writer.printf("mShutdownOnNextSuspend: %b\n", mShutdownOnNextSuspend);
writer.printf("mShutdownOnFinish: %b\n", mShutdownOnFinish);
writer.printf("mShutdownPollingIntervalMs: %d\n", mShutdownPollingIntervalMs);
writer.printf("mShutdownPrepareTimeMs: %d\n", mShutdownPrepareTimeMs);
writer.printf("mRebootAfterGarageMode: %b\n", mRebootAfterGarageMode);
writer.printf("mSwitchGuestUserBeforeSleep: %b\n", mSwitchGuestUserBeforeSleep);
writer.printf("mCurrentPowerPolicyId: %s\n", mCurrentPowerPolicyId);
writer.printf("mPendingPowerPolicyId: %s\n", mPendingPowerPolicyId);
writer.printf("mCurrentPowerPolicyGroupId: %s\n", mCurrentPowerPolicyGroupId);
writer.printf("mIsPowerPolicyLocked: %b\n", mIsPowerPolicyLocked);
writer.printf("mMaxSuspendWaitDurationMs: %d\n", mMaxSuspendWaitDurationMs);
writer.printf("config_maxSuspendWaitDuration: %d\n", getMaxSuspendWaitDurationConfig());
writer.printf("# of power policy change listener: %d\n",
mPowerPolicyListeners.getRegisteredCallbackCount());
writer.printf("mFactoryResetCallback: %s\n", mFactoryResetCallback);
}
mPolicyReader.dump(writer);
mPowerComponentHandler.dump(writer);
mSilentModeHandler.dump(writer);
}
@Override
public void onApPowerStateChange(PowerState state) {
synchronized (mLock) {
mPendingPowerStates.addFirst(new CpmsState(state));
mLock.notify();
}
mHandler.handlePowerStateChange();
}
@VisibleForTesting
void setStateForWakeUp() {
mSilentModeHandler.init();
synchronized (mLock) {
mIsBooting = false;
}
handleWaitForVhal(new CpmsState(CpmsState.WAIT_FOR_VHAL,
CarPowerStateListener.WAIT_FOR_VHAL));
Slogf.d(TAG, "setStateForTesting(): mIsBooting is set to false and power state is switched "
+ "to Wait For Vhal");
}
/**
* Initiate state change from CPMS directly.
*/
private void onApPowerStateChange(int apState, int carPowerStateListenerState) {
CpmsState newState = new CpmsState(apState, carPowerStateListenerState);
synchronized (mLock) {
if (newState.mState == CpmsState.WAIT_FOR_FINISH) {
// We are ready to shut down. Suppress this transition if
// there is a request to cancel the shutdown (WAIT_FOR_VHAL).
for (int idx = 0; idx < mPendingPowerStates.size(); idx++) {
if (mPendingPowerStates.get(idx).mState == CpmsState.WAIT_FOR_VHAL) {
// Completely ignore this WAIT_FOR_FINISH
return;
}
}
}
mPendingPowerStates.addFirst(newState);
mLock.notify();
}
mHandler.handlePowerStateChange();
}
private void doHandlePowerStateChange() {
CpmsState state;
synchronized (mLock) {
state = mPendingPowerStates.peekFirst();
mPendingPowerStates.clear();
if (state == null) {
Slogf.e(TAG, "Null power state was requested");
return;
}
Slogf.i(TAG, "doHandlePowerStateChange: newState=%s", state.name());
if (!needPowerStateChangeLocked(state)) {
return;
}
// now real power change happens. Whatever was queued before should be all cancelled.
releaseTimerLocked();
mCurrentState = state;
}
mHandler.cancelProcessingComplete();
Slogf.i(TAG, "setCurrentState %s", state);
CarStatsLogHelper.logPowerState(state.mState);
switch (state.mState) {
case CpmsState.WAIT_FOR_VHAL:
handleWaitForVhal(state);
break;
case CpmsState.ON:
handleOn();
break;
case CpmsState.SHUTDOWN_PREPARE:
handleShutdownPrepare(state);
break;
case CpmsState.SIMULATE_SLEEP:
simulateShutdownPrepare();
break;
case CpmsState.WAIT_FOR_FINISH:
handleWaitForFinish(state);
break;
case CpmsState.SUSPEND:
// Received FINISH from VHAL
handleFinish();
break;
default:
// Illegal state
// TODO: Throw exception?
break;
}
}
private void handleWaitForVhal(CpmsState state) {
int carPowerStateListenerState = state.mCarPowerStateListenerState;
// TODO(b/177478420): Restore Wifi, Audio, Location, and Bluetooth, if they are artificially
// modified for S2R.
mSilentModeHandler.querySilentModeHwState();
sendPowerManagerEvent(carPowerStateListenerState);
// Inspect CarPowerStateListenerState to decide which message to send via VHAL
switch (carPowerStateListenerState) {
case CarPowerStateListener.WAIT_FOR_VHAL:
mHal.sendWaitForVhal();
break;
case CarPowerStateListener.SHUTDOWN_CANCELLED:
mShutdownOnNextSuspend = false; // This cancels the "NextSuspend"
mHal.sendShutdownCancel();
break;
case CarPowerStateListener.SUSPEND_EXIT:
mHal.sendSleepExit();
break;
}
if (mWifiAdjustmentForSuspend) restoreWifi();
}
private void updateCarUserNoticeServiceIfNecessary() {
try {
int currentUserId = ActivityManager.getCurrentUser();
UserInfo currentUserInfo = mUserManager.getUserInfo(currentUserId);
CarUserNoticeService carUserNoticeService =
CarLocalServices.getService(CarUserNoticeService.class);
if (currentUserInfo != null && currentUserInfo.isGuest()
&& carUserNoticeService != null) {
Slogf.i(TAG, "Car user notice service will ignore all messages before user "
+ "switch.");
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext.getPackageName(),
ContinuousBlankActivity.class.getName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
carUserNoticeService.ignoreUserNotice(currentUserId);
}
} catch (Exception e) {
Slogf.w(TAG, e, "Cannot ignore user notice for current user");
}
}
@VisibleForTesting
void handleOn() {
if (factoryResetIfNeeded()) return;
// If current user is a Guest User, we want to inform CarUserNoticeService not to show
// notice for current user, and show user notice only for the target user.
if (!mSwitchGuestUserBeforeSleep) {
updateCarUserNoticeServiceIfNecessary();
}
boolean isPreemptive;
synchronized (mLock) {
isPreemptive = mPolicyReader.isPreemptivePowerPolicy(mCurrentPowerPolicyId);
}
if (!mSilentModeHandler.isSilentMode() && isPreemptive) {
cancelPreemptivePowerPolicy();
} else {
applyDefaultPowerPolicyForState(VehicleApPowerStateReport.ON,
PolicyReader.POWER_POLICY_ID_ALL_ON);
}
sendPowerManagerEvent(CarPowerStateListener.ON);
mHal.sendOn();
synchronized (mLock) {
if (mIsBooting) {
Slogf.d(TAG, "handleOn(): called on boot");
mIsBooting = false;
return;
}
}
try {
mUserService.onResume();
} catch (Exception e) {
Slogf.e(TAG, e, "Could not switch user on resume");
}
}
private boolean factoryResetIfNeeded() {
IResultReceiver callback;
synchronized (mLock) {
if (mFactoryResetCallback == null) return false;
callback = mFactoryResetCallback;
}
try {
Slogf.i(TAG, "Factory resetting as it was delayed by user");
callback.send(/* resultCode= */ 0, /* resultData= */ null);
return true;
} catch (Exception e) {
Slogf.wtf(TAG, e, "Should have factory reset, but failed");
return false;
}
}
private void applyDefaultPowerPolicyForState(int state, @Nullable String fallbackPolicyId) {
CarPowerPolicy policy;
synchronized (mLock) {
policy = mPolicyReader
.getDefaultPowerPolicyForState(mCurrentPowerPolicyGroupId, state);
}
if (policy == null && fallbackPolicyId == null) {
Slogf.w(TAG, "No default power policy for %s is found",
PolicyReader.powerStateToString(state));
return;
}
String policyId = policy == null ? fallbackPolicyId : policy.getPolicyId();
applyPowerPolicy(policyId, /* upToDaemon= */ true, /* force= */ false);
}
/**
* Sets the callback used to factory reset the device on resume when the user delayed it.
*/
public void setFactoryResetCallback(IResultReceiver callback) {
synchronized (mLock) {
mFactoryResetCallback = callback;
}
}
/**
* Tells Garage Mode if it should run normally, or just
* exit immediately without indicating 'idle'
* @return True if no idle jobs should be run
* @hide
*/
public boolean garageModeShouldExitImmediately() {
synchronized (mLock) {
return mGarageModeShouldExitImmediately;
}
}
private void handleShutdownPrepare(CpmsState newState) {
// Shutdown on finish if the system doesn't support deep sleep or doesn't allow it.
synchronized (mLock) {
mShutdownOnFinish = mShutdownOnNextSuspend
|| !mHal.isDeepSleepAllowed()
|| !mSystemInterface.isSystemSupportingDeepSleep()
|| !newState.mCanSleep;
mGarageModeShouldExitImmediately = !newState.mCanPostpone;
}
Slogf.i(TAG,
(newState.mCanPostpone
? "starting shutdown prepare with Garage Mode"
: "starting shutdown prepare without Garage Mode"));
makeSureNoUserInteraction();
sendPowerManagerEvent(CarPowerStateListener.SHUTDOWN_PREPARE);
mHal.sendShutdownPrepare();
doHandlePreprocessing();
}
// Simulate system shutdown to Deep Sleep
private void simulateShutdownPrepare() {
Slogf.i(TAG, "starting shutdown prepare");
makeSureNoUserInteraction();
sendPowerManagerEvent(CarPowerStateListener.SHUTDOWN_PREPARE);
mHal.sendShutdownPrepare();
doHandlePreprocessing();
}
private void handleWaitForFinish(CpmsState state) {
sendPowerManagerEvent(state.mCarPowerStateListenerState);
int wakeupSec;
synchronized (mLock) {
// If we're shutting down immediately, don't schedule
// a wakeup time.
wakeupSec = mGarageModeShouldExitImmediately ? 0 : mNextWakeupSec;
}
switch (state.mCarPowerStateListenerState) {
case CarPowerStateListener.SUSPEND_ENTER:
mHal.sendSleepEntry(wakeupSec);
break;
case CarPowerStateListener.SHUTDOWN_ENTER:
mHal.sendShutdownStart(wakeupSec);
break;
}
}
private void handleFinish() {
boolean simulatedMode;
synchronized (mSimulationWaitObject) {
simulatedMode = mInSimulatedDeepSleepMode;
}
boolean mustShutDown;
boolean forceReboot;
synchronized (mLock) {
mustShutDown = mShutdownOnFinish && !simulatedMode;
forceReboot = mRebootAfterGarageMode;
mRebootAfterGarageMode = false;
}
if (forceReboot) {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
if (powerManager == null) {
Slogf.wtf(TAG, "No PowerManager. Cannot reboot.");
} else {
Slogf.i(TAG, "GarageMode has completed. Forcing reboot.");
powerManager.reboot("GarageModeReboot");
throw new AssertionError("Should not return from PowerManager.reboot()");
}
}
// To make Kernel implementation simpler when going into sleep.
if (mWifiAdjustmentForSuspend) disableWifi();
if (mustShutDown) {
// shutdown HU
mSystemInterface.shutdown();
} else {
doHandleDeepSleep(simulatedMode);
}
mShutdownOnNextSuspend = false;
}
private void restoreWifi() {
boolean needToRestore = readWifiModifiedState();
if (needToRestore) {
if (!mWifiManager.isWifiEnabled()) {
Slogf.i(TAG, "Wifi has been enabled to restore the last setting");
mWifiManager.setWifiEnabled(true);
}
// Update the persistent data as wifi is not modified by car framework.
saveWifiModifiedState(false);
}
}
private void disableWifi() {
boolean wifiEnabled = mWifiManager.isWifiEnabled();
boolean wifiModifiedState = readWifiModifiedState();
if (wifiEnabled != wifiModifiedState) {
saveWifiModifiedState(wifiEnabled);
}
if (!wifiEnabled) return;
mWifiManager.setWifiEnabled(false);
Slogf.i(TAG, "Wifi has been disabled and the last setting was saved");
}
private void saveWifiModifiedState(boolean forciblyDisabled) {
FileOutputStream fos;
try {
fos = mWifiStateFile.startWrite();
} catch (IOException e) {
Slogf.e(TAG, e, "Cannot create %s", WIFI_STATE_FILENAME);
return;
}
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
writer.write(forciblyDisabled ? WIFI_STATE_MODIFIED : WIFI_STATE_ORIGINAL);
writer.newLine();
writer.flush();
mWifiStateFile.finishWrite(fos);
} catch (IOException e) {
mWifiStateFile.failWrite(fos);
Slogf.e(TAG, e, "Writing %s failed", WIFI_STATE_FILENAME);
}
}
private boolean readWifiModifiedState() {
boolean needToRestore = false;
boolean invalidState = false;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(mWifiStateFile.openRead(), StandardCharsets.UTF_8))) {
String line = reader.readLine();
if (line == null) {
needToRestore = false;
invalidState = true;
} else {
line = line.trim();
needToRestore = WIFI_STATE_MODIFIED.equals(line);
invalidState = !(needToRestore || WIFI_STATE_ORIGINAL.equals(line));
}
} catch (IOException e) {
// If a file named wifi_state doesn't exist, we will not modify Wifi at system start.
Slogf.w(TAG, "Failed to read %s: %s", WIFI_STATE_FILENAME, e);
return false;
}
if (invalidState) {
mWifiStateFile.delete();
}
return needToRestore;
}
@GuardedBy("mLock")
private void releaseTimerLocked() {
if (mTimer != null) {
mTimer.cancel();
}
mTimer = null;
mTimerActive = false;
}
private void doHandlePreprocessing() {
int intervalMs;
int pollingCount;
synchronized (mLock) {
intervalMs = mShutdownPollingIntervalMs;
pollingCount = (mShutdownPrepareTimeMs / mShutdownPollingIntervalMs) + 1;
}
if (Build.IS_USERDEBUG || Build.IS_ENG) {
int shutdownPrepareTimeOverrideInSecs =
SystemProperties.getInt(PROP_MAX_GARAGE_MODE_DURATION_OVERRIDE, -1);
if (shutdownPrepareTimeOverrideInSecs >= 0) {
pollingCount =
(shutdownPrepareTimeOverrideInSecs * 1000 / intervalMs)
+ 1;
Slogf.i(TAG, "Garage mode duration overridden secs: %d",
shutdownPrepareTimeOverrideInSecs);
}
}
Slogf.i(TAG, "processing before shutdown expected for: %dms, adding polling:%d",
mShutdownPrepareTimeMs, pollingCount);
boolean allAreComplete;
synchronized (mLock) {
mProcessingStartTime = SystemClock.elapsedRealtime();
releaseTimerLocked();
allAreComplete = mListenersWeAreWaitingFor.isEmpty();
if (allAreComplete) {
Slogf.i(TAG, "Listener queue is empty, don't start polling");
} else {
mTimer = new Timer();
mTimerActive = true;
mTimer.scheduleAtFixedRate(
new ShutdownProcessingTimerTask(pollingCount),
/* delay= */ 0,
intervalMs);
}
}
if (allAreComplete) {
signalComplete();
}
// allowUserSwitch value doesn't matter for onSuspend = true
mUserService.onSuspend();
}
private void sendPowerManagerEvent(int newState) {
// Broadcast to the listeners that do not signal completion
notifyListeners(mPowerManagerListeners, newState);
// SHUTDOWN_PREPARE is the only state where we need
// to maintain callbacks from listener components.
boolean allowCompletion = (newState == CarPowerStateListener.SHUTDOWN_PREPARE);
// Fully populate mListenersWeAreWaitingFor before calling any onStateChanged()
// for the listeners that signal completion.
// Otherwise, if the first listener calls finish() synchronously, we will
// see the list go empty and we will think that we are done.
boolean haveSomeCompleters = false;
PowerManagerCallbackList<ICarPowerStateListener> completingListeners =
new PowerManagerCallbackList(l -> { });
synchronized (mLock) {
mListenersWeAreWaitingFor.clear();
int idx = mPowerManagerListenersWithCompletion.beginBroadcast();
while (idx-- > 0) {
ICarPowerStateListener listener =
mPowerManagerListenersWithCompletion.getBroadcastItem(idx);
completingListeners.register(listener);
if (allowCompletion) {
mListenersWeAreWaitingFor.add(listener.asBinder());
haveSomeCompleters = true;
}
}
mPowerManagerListenersWithCompletion.finishBroadcast();
}
// Broadcast to the listeners that DO signal completion
notifyListeners(completingListeners, newState);
if (allowCompletion && !haveSomeCompleters) {
// No jobs need to signal completion. So we are now complete.
signalComplete();
}
}
private void notifyListeners(PowerManagerCallbackList<ICarPowerStateListener> listenerList,
int newState) {
int idx = listenerList.beginBroadcast();
while (idx-- > 0) {
ICarPowerStateListener listener = listenerList.getBroadcastItem(idx);
try {
listener.onStateChanged(newState);
} catch (RemoteException e) {
// It's likely the connection snapped. Let binder death handle the situation.
Slogf.e(TAG, e, "onStateChanged() call failed");
}
}
listenerList.finishBroadcast();
}
private void doHandleDeepSleep(boolean simulatedMode) {
int status = applyPreemptivePowerPolicy(PolicyReader.POWER_POLICY_ID_SUSPEND_TO_RAM);
if (status != PolicyOperationStatus.OK) {
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(status));
}
// keep holding partial wakelock to prevent entering sleep before enterDeepSleep call
// enterDeepSleep should force sleep entry even if wake lock is kept.
mSystemInterface.switchToPartialWakeLock();
mHandler.cancelProcessingComplete();
synchronized (mLock) {
mLastSleepEntryTime = SystemClock.elapsedRealtime();
}
int nextListenerState;
if (simulatedMode) {
simulateSleepByWaiting();
nextListenerState = CarPowerStateListener.SHUTDOWN_CANCELLED;
} else {
boolean sleepSucceeded = suspendWithRetries();
if (!sleepSucceeded) {
// Suspend failed and we shut down instead.
// We either won't get here at all or we will power off very soon.
return;
}
// We suspended and have now resumed
nextListenerState = CarPowerStateListener.SUSPEND_EXIT;
}
synchronized (mLock) {
// Any wakeup time from before is no longer valid.
mNextWakeupSec = 0;
}
Slogf.i(TAG, "Resuming after suspending");
mSystemInterface.refreshDisplayBrightness();
onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, nextListenerState);
}
private boolean needPowerStateChangeLocked(@NonNull CpmsState newState) {
if (mCurrentState == null) {
return true;
} else if (mCurrentState.equals(newState)) {
Slogf.d(TAG, "Requested state is already in effect: %s", newState.name());
return false;
}
// The following switch/case enforces the allowed state transitions.
boolean transitionAllowed = false;
switch (mCurrentState.mState) {
case CpmsState.WAIT_FOR_VHAL:
transitionAllowed = (newState.mState == CpmsState.ON)
|| (newState.mState == CpmsState.SHUTDOWN_PREPARE);
break;
case CpmsState.SUSPEND:
transitionAllowed = newState.mState == CpmsState.WAIT_FOR_VHAL;
break;
case CpmsState.ON:
transitionAllowed = (newState.mState == CpmsState.SHUTDOWN_PREPARE)
|| (newState.mState == CpmsState.SIMULATE_SLEEP);
break;
case CpmsState.SHUTDOWN_PREPARE:
// If VHAL sends SHUTDOWN_IMMEDIATELY or SLEEP_IMMEDIATELY while in
// SHUTDOWN_PREPARE state, do it.
transitionAllowed =
((newState.mState == CpmsState.SHUTDOWN_PREPARE) && !newState.mCanPostpone)
|| (newState.mState == CpmsState.WAIT_FOR_FINISH)
|| (newState.mState == CpmsState.WAIT_FOR_VHAL);
break;
case CpmsState.SIMULATE_SLEEP:
transitionAllowed = true;
break;
case CpmsState.WAIT_FOR_FINISH:
transitionAllowed = (newState.mState == CpmsState.SUSPEND
|| newState.mState == CpmsState.WAIT_FOR_VHAL);
break;
default:
Slogf.e(TAG, "Unexpected current state: currentState=%s, newState=%s",
mCurrentState.name(), newState.name());
transitionAllowed = true;
}
if (!transitionAllowed) {
Slogf.e(TAG, "Requested power transition is not allowed: %s --> %s",
mCurrentState.name(), newState.name());
}
return transitionAllowed;
}
private void doHandleProcessingComplete() {
int listenerState;
synchronized (mLock) {
releaseTimerLocked();
if (!mShutdownOnFinish && mLastSleepEntryTime > mProcessingStartTime) {
// entered sleep after processing start. So this could be duplicate request.
Slogf.w(TAG, "Duplicate sleep entry request, ignore");
return;
}
listenerState = mShutdownOnFinish
? CarPowerStateListener.SHUTDOWN_ENTER : CarPowerStateListener.SUSPEND_ENTER;
}
onApPowerStateChange(CpmsState.WAIT_FOR_FINISH, listenerState);
}
@Override
public void onDisplayBrightnessChange(int brightness) {
mHandler.handleDisplayBrightnessChange(brightness);
}
private void doHandleDisplayBrightnessChange(int brightness) {
mSystemInterface.setDisplayBrightness(brightness);
}
private void doHandleMainDisplayStateChange(boolean on) {
Slogf.w(TAG, "Unimplemented: doHandleMainDisplayStateChange() - on = %b", on);
}
/**
* Handles when a main display changes.
*/
public void handleMainDisplayChanged(boolean on) {
mHandler.handleMainDisplayStateChange(on);
}
/**
* Send display brightness to VHAL.
* @param brightness value 0-100%
*/
public void sendDisplayBrightness(int brightness) {
mHal.sendDisplayBrightness(brightness);
}
/**
* Get the PowerHandler that we use to change power states
*/
public Handler getHandler() {
return mHandler;
}
// Binder interface for general use.
// The listener is not required (or allowed) to call finished().
@Override
public void registerListener(ICarPowerStateListener listener) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
mPowerManagerListeners.register(listener);
}
// Binder interface for Car services only.
// After the listener completes its processing, it must call finished().
@Override
public void registerListenerWithCompletion(ICarPowerStateListener listener) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
ICarImpl.assertCallingFromSystemProcessOrSelf();
mPowerManagerListenersWithCompletion.register(listener);
// TODO: Need to send current state to newly registered listener? If so, need to handle
// completion for SHUTDOWN_PREPARE state
}
@Override
public void unregisterListener(ICarPowerStateListener listener) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
doUnregisterListener(listener);
}
@Override
public void requestShutdownOnNextSuspend() {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
synchronized (mLock) {
mShutdownOnNextSuspend = true;
}
}
@Override
public void finished(ICarPowerStateListener listener) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
ICarImpl.assertCallingFromSystemProcessOrSelf();
finishedImpl(listener.asBinder());
}
@Override
public void scheduleNextWakeupTime(int seconds) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
if (seconds < 0) {
Slogf.w(TAG, "Next wake up time is negative. Ignoring!");
return;
}
boolean timedWakeupAllowed = mHal.isTimedWakeupAllowed();
synchronized (mLock) {
if (!timedWakeupAllowed) {
Slogf.w(TAG, "Setting timed wakeups are disabled in HAL. Skipping");
mNextWakeupSec = 0;
return;
}
if (mNextWakeupSec == 0 || mNextWakeupSec > seconds) {
// The new value is sooner than the old value. Take the new value.
mNextWakeupSec = seconds;
} else {
Slogf.d(TAG, "Tried to schedule next wake up, but already had shorter "
+ "scheduled time");
}
}
}
@Override
public int getPowerState() {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
synchronized (mLock) {
return (mCurrentState == null) ? CarPowerStateListener.INVALID
: mCurrentState.mCarPowerStateListenerState;
}
}
/**
* @see android.car.hardware.power.CarPowerManager#getCurrentPowerPolicy
*/
@Override
public CarPowerPolicy getCurrentPowerPolicy() {
ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_POWER_POLICY);
return mPowerComponentHandler.getAccumulatedPolicy();
}
/**
* @see android.car.hardware.power.CarPowerManager#applyPowerPolicy
*/
@Override
public void applyPowerPolicy(String policyId) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_POWER_POLICY);
Preconditions.checkArgument(policyId != null, "policyId cannot be null");
Preconditions.checkArgument(!policyId.startsWith(PolicyReader.SYSTEM_POWER_POLICY_PREFIX),
"System power policy cannot be applied by apps");
int status = applyPowerPolicy(policyId, /* upToDaemon= */ true, /* force= */ false);
if (status != PolicyOperationStatus.OK) {
throw new IllegalArgumentException(PolicyOperationStatus.errorCodeToString(status));
}
}
/**
* @see android.car.hardware.power.CarPowerManager#setPowerPolicyGroup
*/
@Override
public void setPowerPolicyGroup(String policyGroupId) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_POWER_POLICY);
Preconditions.checkArgument(policyGroupId != null, "policyGroupId cannot be null");
int status = setCurrentPowerPolicyGroup(policyGroupId);
if (status != PolicyOperationStatus.OK) {
throw new IllegalArgumentException(PolicyOperationStatus.errorCodeToString(status));
}
}
/**
* @see android.car.hardware.power.CarPowerManager#addPowerPolicyListener
*/
@Override
public void addPowerPolicyListener(CarPowerPolicyFilter filter,
ICarPowerPolicyListener listener) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_POWER_POLICY);
mPowerPolicyListeners.register(listener, filter);
}
/**
* @see android.car.hardware.power.CarPowerManager#removePowerPolicyListener
*/
@Override
public void removePowerPolicyListener(ICarPowerPolicyListener listener) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_POWER_POLICY);
mPowerPolicyListeners.unregister(listener);
}
void notifySilentModeChange(boolean silent) {
Slogf.i(TAG, "Silent mode is set to %b", silent);
if (silent) {
applyPreemptivePowerPolicy(PolicyReader.POWER_POLICY_ID_NO_USER_INTERACTION);
} else {
cancelPreemptivePowerPolicy();
}
}
private void doUnregisterListener(ICarPowerStateListener listener) {
mPowerManagerListeners.unregister(listener);
boolean found = mPowerManagerListenersWithCompletion.unregister(listener);
if (found) {
// Remove this from the completion list (if it's there)
finishedImpl(listener.asBinder());
}
}
private void finishedImpl(IBinder binder) {
boolean allAreComplete;
synchronized (mLock) {
mListenersWeAreWaitingFor.remove(binder);
allAreComplete = mListenersWeAreWaitingFor.isEmpty();
}
if (allAreComplete) {
signalComplete();
}
}
private void signalComplete() {
if (mCurrentState.mState == CpmsState.SHUTDOWN_PREPARE
|| mCurrentState.mState == CpmsState.SIMULATE_SLEEP) {
PowerHandler powerHandler;
// All apps are ready to shutdown/suspend.
synchronized (mLock) {
if (!mShutdownOnFinish) {
if (mLastSleepEntryTime > mProcessingStartTime
&& mLastSleepEntryTime < SystemClock.elapsedRealtime()) {
Slogf.i(TAG, "signalComplete: Already slept!");
return;
}
}
powerHandler = mHandler;
}
Slogf.i(TAG, "Apps are finished, call handleProcessingComplete()");
powerHandler.handleProcessingComplete();
}
}
private void initializePowerPolicy() {
Slogf.i(TAG, "CPMS is taking control from carpowerpolicyd");
ICarPowerPolicySystemNotification daemon;
synchronized (mLock) {
daemon = mCarPowerPolicyDaemon;
}
PolicyState state;
if (daemon != null) {
try {
state = daemon.notifyCarServiceReady();
} catch (RemoteException e) {
Slogf.e(TAG, e, "Failed to tell car power policy daemon that CarService is ready");
return;
}
} else {
Slogf.w(TAG, "Failed to notify car service is ready. car power policy daemon is not "
+ "available");
return;
}
String currentPowerPolicyId;
String currentPolicyGroupId;
synchronized (mLock) {
mHasControlOverDaemon = true;
currentPowerPolicyId = mCurrentPowerPolicyId;
currentPolicyGroupId = mCurrentPowerPolicyGroupId;
}
// If the current power policy or the policy group has been modified by CPMS, we ignore
// the power policy or the policy group passed from car power policy daemon, and notifies
// the current power policy to the daemon.
if (currentPowerPolicyId == null || currentPowerPolicyId.isEmpty()) {
int status = applyPowerPolicy(state.policyId, /* upToDaemon= */ false,
/* force= */ false);
if (status != PolicyOperationStatus.OK) {
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(status));
}
} else {
notifyPowerPolicyChangeToDaemon(currentPowerPolicyId, /* force= */ true);
}
if (currentPolicyGroupId == null || currentPolicyGroupId.isEmpty()) {
int status = setCurrentPowerPolicyGroup(state.policyGroupId);
if (status != PolicyOperationStatus.OK) {
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(status));
}
}
mSilentModeHandler.init();
}
@PolicyOperationStatus.ErrorCode
private int setCurrentPowerPolicyGroup(String policyGroupId) {
if (!mPolicyReader.isPowerPolicyGroupAvailable(policyGroupId)) {
int error = PolicyOperationStatus.ERROR_SET_POWER_POLICY_GROUP;
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error,
policyGroupId + " is not registered"));
return error;
}
synchronized (mLock) {
mCurrentPowerPolicyGroupId = policyGroupId;
}
return PolicyOperationStatus.OK;
}
@PolicyOperationStatus.ErrorCode
private int applyPowerPolicy(@Nullable String policyId, boolean upToDaemon, boolean force) {
CarPowerPolicy policy = mPolicyReader.getPowerPolicy(policyId);
if (policy == null) {
int error = PolicyOperationStatus.ERROR_APPLY_POWER_POLICY;
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, policyId));
return error;
}
synchronized (mLock) {
if (mIsPowerPolicyLocked) {
Slogf.i(TAG, "Power policy is locked. The request policy(%s) will be applied when "
+ "power policy becomes unlocked", policyId);
mPendingPowerPolicyId = policyId;
return PolicyOperationStatus.OK;
}
mCurrentPowerPolicyId = policyId;
}
mPowerComponentHandler.applyPowerPolicy(policy);
notifyPowerPolicyChange(policyId, upToDaemon, force);
Slogf.i(TAG, "The current power policy is %s", policyId);
return PolicyOperationStatus.OK;
}
@PolicyOperationStatus.ErrorCode
private int applyPreemptivePowerPolicy(String policyId) {
CarPowerPolicy policy = mPolicyReader.getPreemptivePowerPolicy(policyId);
if (policy == null) {
int error = PolicyOperationStatus.ERROR_NOT_REGISTERED_POWER_POLICY_ID;
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error, policyId));
return error;
}
synchronized (mLock) {
mIsPowerPolicyLocked = true;
if (!mPolicyReader.isPreemptivePowerPolicy(mCurrentPowerPolicyId)) {
mPendingPowerPolicyId = mCurrentPowerPolicyId;
}
mCurrentPowerPolicyId = policyId;
}
mPowerComponentHandler.applyPowerPolicy(policy);
notifyPowerPolicyChange(policyId, /* upToDaemon= */ true, /* force= */ true);
Slogf.i(TAG, "The current power policy is %s", policyId);
return PolicyOperationStatus.OK;
}
private void cancelPreemptivePowerPolicy() {
String policyId;
synchronized (mLock) {
if (!mIsPowerPolicyLocked) {
Slogf.w(TAG, "Failed to cancel system power policy: the current policy is not the "
+ "system power policy");
return;
}
mIsPowerPolicyLocked = false;
policyId = mPendingPowerPolicyId;
mPendingPowerPolicyId = null;
}
int status = applyPowerPolicy(policyId, /* upToDaemon= */ true, /* force= */ true);
if (status != PolicyOperationStatus.OK) {
Slogf.w(TAG, "Failed to cancel system power policy: %s",
PolicyOperationStatus.errorCodeToString(status));
}
}
private void notifyPowerPolicyChangeToDaemon(String policyId, boolean force) {
ICarPowerPolicySystemNotification daemon;
boolean hadPendingPolicyNotification;
synchronized (mLock) {
daemon = mCarPowerPolicyDaemon;
if (daemon == null) {
Slogf.e(TAG, "Failed to notify car power policy daemon: the daemon is not ready");
return;
}
if (!mHasControlOverDaemon) {
Slogf.w(TAG, "Notifying policy change is deferred: CPMS has not yet taken control");
return;
}
}
try {
daemon.notifyPowerPolicyChange(policyId, force);
} catch (RemoteException | IllegalStateException e) {
Slogf.e(TAG, e, "Failed to notify car power policy daemon of a new power policy(%s)",
policyId);
}
}
private void notifyPowerPolicyChange(String policyId, boolean upToDaemon, boolean force) {
// Notify system clients
if (upToDaemon) {
notifyPowerPolicyChangeToDaemon(policyId, force);
}
// Notify Java clients
CarPowerPolicy accumulatedPolicy = mPowerComponentHandler.getAccumulatedPolicy();
CarPowerPolicy appliedPolicy = mPolicyReader.isPreemptivePowerPolicy(policyId)
? mPolicyReader.getPreemptivePowerPolicy(policyId)
: mPolicyReader.getPowerPolicy(policyId);
if (appliedPolicy == null) {
Slogf.wtf(TAG, "The new power policy(%s) should exist", policyId);
}
int idx = mPowerPolicyListeners.beginBroadcast();
while (idx-- > 0) {
ICarPowerPolicyListener listener = mPowerPolicyListeners.getBroadcastItem(idx);
CarPowerPolicyFilter filter =
(CarPowerPolicyFilter) mPowerPolicyListeners.getBroadcastCookie(idx);
if (!mPowerComponentHandler.isComponentChanged(filter)) {
continue;
}
try {
listener.onPolicyChanged(appliedPolicy, accumulatedPolicy);
} catch (RemoteException e) {
// It's likely the connection snapped. Let binder death handle the situation.
Slogf.e(TAG, e, "onPolicyChanged() call failed: policyId = %s", policyId);
}
}
mPowerPolicyListeners.finishBroadcast();
}
private void makeSureNoUserInteraction() {
mSilentModeHandler.updateKernelSilentMode(true);
int status = applyPreemptivePowerPolicy(PolicyReader.POWER_POLICY_ID_NO_USER_INTERACTION);
if (status != PolicyOperationStatus.OK) {
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(status));
}
}
private void connectToPowerPolicyDaemon() {
synchronized (mLock) {
if (mCarPowerPolicyDaemon != null || mConnectionInProgress) {
return;
}
mConnectionInProgress = true;
}
connectToDaemonHelper(CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY);
}
private void connectToDaemonHelper(int retryCount) {
if (retryCount <= 0) {
synchronized (mLock) {
mConnectionInProgress = false;
}
Slogf.e(TAG, "Cannot reconnect to car power policyd daemon after retrying %d times",
CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY);
return;
}
if (makeBinderConnection()) {
Slogf.i(TAG, "Connected to car power policy daemon");
initializePowerPolicy();
return;
}
mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
CarPowerManagementService::connectToDaemonHelper,
CarPowerManagementService.this, retryCount - 1),
CAR_POWER_POLICY_DAEMON_BIND_RETRY_INTERVAL_MS);
}
private boolean makeBinderConnection() {
long currentTimeMs = SystemClock.uptimeMillis();
IBinder binder = ServiceManager.getService(CAR_POWER_POLICY_DAEMON_INTERFACE);
if (binder == null) {
Slogf.w(TAG, "Finding car power policy daemon failed. Power policy management is not "
+ "supported");
return false;
}
long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs;
if (elapsedTimeMs > CAR_POWER_POLICY_DAEMON_FIND_MARGINAL_TIME_MS) {
Slogf.wtf(TAG, "Finding car power policy daemon took too long(%dms)", elapsedTimeMs);
}
ICarPowerPolicySystemNotification daemon =
ICarPowerPolicySystemNotification.Stub.asInterface(binder);
if (daemon == null) {
Slogf.w(TAG, "Getting car power policy daemon interface failed. Power policy management"
+ " is not supported");
return false;
}
synchronized (mLock) {
mCarPowerPolicyDaemon = daemon;
mConnectionInProgress = false;
}
mBinderHandler = new BinderHandler(daemon);
mBinderHandler.linkToDeath();
return true;
}
private final class BinderHandler implements IBinder.DeathRecipient {
private ICarPowerPolicySystemNotification mDaemon;
private BinderHandler(ICarPowerPolicySystemNotification daemon) {
mDaemon = daemon;
}
@Override
public void binderDied() {
Slogf.w(TAG, "Car power policy daemon died: reconnecting");
unlinkToDeath();
mDaemon = null;
synchronized (mLock) {
mCarPowerPolicyDaemon = null;
mHasControlOverDaemon = false;
}
mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
CarPowerManagementService::connectToDaemonHelper,
CarPowerManagementService.this, CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY),
CAR_POWER_POLICY_DAEMON_BIND_RETRY_INTERVAL_MS);
}
private void linkToDeath() {
if (mDaemon == null) {
return;
}
IBinder binder = mDaemon.asBinder();
if (binder == null) {
Slogf.w(TAG, "Linking to binder death recipient skipped");
return;
}
try {
binder.linkToDeath(this, 0);
} catch (RemoteException e) {
mDaemon = null;
Slogf.w(TAG, e, "Linking to binder death recipient failed: %s");
}
}
private void unlinkToDeath() {
if (mDaemon == null) {
return;
}
IBinder binder = mDaemon.asBinder();
if (binder == null) {
Slogf.w(TAG, "Unlinking from binder death recipient skipped");
return;
}
binder.unlinkToDeath(this, 0);
}
}
private static final class PowerHandler extends Handler {
private static final String TAG = PowerHandler.class.getSimpleName();
private static final int MSG_POWER_STATE_CHANGE = 0;
private static final int MSG_DISPLAY_BRIGHTNESS_CHANGE = 1;
private static final int MSG_MAIN_DISPLAY_STATE_CHANGE = 2;
private static final int MSG_PROCESSING_COMPLETE = 3;
// Do not handle this immediately but with some delay as there can be a race between
// display off due to rear view camera and delivery to here.
private static final long MAIN_DISPLAY_EVENT_DELAY_MS = 500;
private final WeakReference<CarPowerManagementService> mService;
private PowerHandler(Looper looper, CarPowerManagementService service) {
super(looper);
mService = new WeakReference<CarPowerManagementService>(service);
}
private void handlePowerStateChange() {
Message msg = obtainMessage(MSG_POWER_STATE_CHANGE);
sendMessage(msg);
}
private void handleDisplayBrightnessChange(int brightness) {
Message msg = obtainMessage(MSG_DISPLAY_BRIGHTNESS_CHANGE, brightness, 0);
sendMessage(msg);
}
private void handleMainDisplayStateChange(boolean on) {
removeMessages(MSG_MAIN_DISPLAY_STATE_CHANGE);
Message msg = obtainMessage(MSG_MAIN_DISPLAY_STATE_CHANGE, Boolean.valueOf(on));
sendMessageDelayed(msg, MAIN_DISPLAY_EVENT_DELAY_MS);
}
private void handleProcessingComplete() {
removeMessages(MSG_PROCESSING_COMPLETE);
Message msg = obtainMessage(MSG_PROCESSING_COMPLETE);
sendMessage(msg);
}
private void cancelProcessingComplete() {
removeMessages(MSG_PROCESSING_COMPLETE);
}
private void cancelAll() {
removeMessages(MSG_POWER_STATE_CHANGE);
removeMessages(MSG_DISPLAY_BRIGHTNESS_CHANGE);
removeMessages(MSG_MAIN_DISPLAY_STATE_CHANGE);
removeMessages(MSG_PROCESSING_COMPLETE);
}
@Override
public void handleMessage(Message msg) {
CarPowerManagementService service = mService.get();
if (service == null) {
Slogf.i(TAG, "handleMessage null service");
return;
}
switch (msg.what) {
case MSG_POWER_STATE_CHANGE:
service.doHandlePowerStateChange();
break;
case MSG_DISPLAY_BRIGHTNESS_CHANGE:
service.doHandleDisplayBrightnessChange(msg.arg1);
break;
case MSG_MAIN_DISPLAY_STATE_CHANGE:
service.doHandleMainDisplayStateChange((Boolean) msg.obj);
break;
case MSG_PROCESSING_COMPLETE:
service.doHandleProcessingComplete();
break;
}
}
}
private class ShutdownProcessingTimerTask extends TimerTask {
private final int mExpirationCount;
private int mCurrentCount;
private ShutdownProcessingTimerTask(int expirationCount) {
mExpirationCount = expirationCount;
mCurrentCount = 0;
}
@Override
public void run() {
synchronized (mLock) {
if (!mTimerActive) {
// Ignore timer expiration since we got cancelled
return;
}
mCurrentCount++;
if (mCurrentCount > mExpirationCount) {
PowerHandler handler;
releaseTimerLocked();
handler = mHandler;
handler.handleProcessingComplete();
} else {
mHal.sendShutdownPostpone(SHUTDOWN_EXTEND_MAX_MS);
}
}
}
}
// Send the command to enter Suspend to RAM.
// If the command is not successful, try again with an exponential back-off.
// If it fails repeatedly, send the command to shut down.
// If we decide to go to a different power state, abort this retry mechanism.
// Returns true if we successfully suspended.
private boolean suspendWithRetries() {
long retryIntervalMs = INITIAL_SUSPEND_RETRY_INTERVAL_MS;
long totalWaitDurationMs = 0;
while (true) {
Slogf.i(TAG, "Entering Suspend to RAM");
boolean suspendSucceeded = mSystemInterface.enterDeepSleep();
if (suspendSucceeded) {
return true;
}
if (totalWaitDurationMs >= mMaxSuspendWaitDurationMs) {
break;
}
// We failed to suspend. Block the thread briefly and try again.
synchronized (mLock) {
if (mPendingPowerStates.isEmpty()) {
Slogf.w(TAG, "Failed to Suspend; will retry after %dms", retryIntervalMs);
try {
mLock.wait(retryIntervalMs);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
totalWaitDurationMs += retryIntervalMs;
retryIntervalMs = Math.min(retryIntervalMs * 2, MAX_RETRY_INTERVAL_MS);
}
// Check for a new power state now, before going around the loop again
if (!mPendingPowerStates.isEmpty()) {
Slogf.i(TAG, "Terminating the attempt to Suspend to RAM");
return false;
}
}
}
// Too many failures trying to suspend. Shut down.
Slogf.w(TAG, "Could not Suspend to RAM after %dms long trial. Shutting down.",
totalWaitDurationMs);
mSystemInterface.shutdown();
return false;
}
private static class CpmsState {
// NOTE: When modifying states below, make sure to update CarPowerStateChanged.State in
// frameworks/proto_logging/stats/atoms.proto also.
public static final int WAIT_FOR_VHAL = 0;
public static final int ON = 1;
public static final int SHUTDOWN_PREPARE = 2;
public static final int WAIT_FOR_FINISH = 3;
public static final int SUSPEND = 4;
public static final int SIMULATE_SLEEP = 5;
/* Config values from AP_POWER_STATE_REQ */
public final boolean mCanPostpone;
public final boolean mCanSleep;
/* Message sent to CarPowerStateListener in response to this state */
public final int mCarPowerStateListenerState;
/* One of the above state variables */
public final int mState;
/**
* This constructor takes a PowerHalService.PowerState object and creates the corresponding
* CPMS state from it.
*/
CpmsState(PowerState halPowerState) {
switch (halPowerState.mState) {
case VehicleApPowerStateReq.ON:
this.mCanPostpone = false;
this.mCanSleep = false;
this.mCarPowerStateListenerState = cpmsStateToPowerStateListenerState(ON);
this.mState = ON;
break;
case VehicleApPowerStateReq.SHUTDOWN_PREPARE:
this.mCanPostpone = halPowerState.canPostponeShutdown();
this.mCanSleep = halPowerState.canEnterDeepSleep();
this.mCarPowerStateListenerState = cpmsStateToPowerStateListenerState(
SHUTDOWN_PREPARE);
this.mState = SHUTDOWN_PREPARE;
break;
case VehicleApPowerStateReq.CANCEL_SHUTDOWN:
this.mCanPostpone = false;
this.mCanSleep = false;
this.mCarPowerStateListenerState = CarPowerStateListener.SHUTDOWN_CANCELLED;
this.mState = WAIT_FOR_VHAL;
break;
case VehicleApPowerStateReq.FINISHED:
this.mCanPostpone = false;
this.mCanSleep = false;
this.mCarPowerStateListenerState = cpmsStateToPowerStateListenerState(SUSPEND);
this.mState = SUSPEND;
break;
default:
// Illegal state from PowerState. Throw an exception?
this.mCanPostpone = false;
this.mCanSleep = false;
this.mCarPowerStateListenerState = 0;
this.mState = 0;
break;
}
}
CpmsState(int state, int carPowerStateListenerState) {
this.mCanPostpone = (state == SIMULATE_SLEEP);
this.mCanSleep = (state == SIMULATE_SLEEP);
this.mCarPowerStateListenerState = carPowerStateListenerState;
this.mState = state;
}
public String name() {
String baseName;
switch(mState) {
case WAIT_FOR_VHAL: baseName = "WAIT_FOR_VHAL"; break;
case ON: baseName = "ON"; break;
case SHUTDOWN_PREPARE: baseName = "SHUTDOWN_PREPARE"; break;
case WAIT_FOR_FINISH: baseName = "WAIT_FOR_FINISH"; break;
case SUSPEND: baseName = "SUSPEND"; break;
case SIMULATE_SLEEP: baseName = "SIMULATE_SLEEP"; break;
default: baseName = "<unknown>"; break;
}
return baseName + "(" + mState + ")";
}
private static int cpmsStateToPowerStateListenerState(int state) {
int powerStateListenerState = 0;
// Set the CarPowerStateListenerState based on current state
switch (state) {
case ON:
powerStateListenerState = CarPowerStateListener.ON;
break;
case SHUTDOWN_PREPARE:
powerStateListenerState = CarPowerStateListener.SHUTDOWN_PREPARE;
break;
case SUSPEND:
powerStateListenerState = CarPowerStateListener.SUSPEND_ENTER;
break;
case WAIT_FOR_VHAL:
case WAIT_FOR_FINISH:
default:
// Illegal state for this constructor. Throw an exception?
break;
}
return powerStateListenerState;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CpmsState)) {
return false;
}
CpmsState that = (CpmsState) o;
return this.mState == that.mState
&& this.mCanSleep == that.mCanSleep
&& this.mCanPostpone == that.mCanPostpone
&& this.mCarPowerStateListenerState == that.mCarPowerStateListenerState;
}
@Override
public String toString() {
return "CpmsState canSleep:" + mCanSleep + ", canPostpone=" + mCanPostpone
+ ", carPowerStateListenerState=" + mCarPowerStateListenerState
+ ", CpmsState=" + this.name();
}
}
/**
* Resume after a manually-invoked suspend.
* Invoked using "adb shell dumpsys activity service com.android.car resume".
*/
public void forceSimulatedResume() {
PowerHandler handler;
synchronized (mLock) {
// Cancel Garage Mode in case it's running
mPendingPowerStates.addFirst(new CpmsState(CpmsState.WAIT_FOR_VHAL,
CarPowerStateListener.SHUTDOWN_CANCELLED));
mLock.notify();
handler = mHandler;
}
handler.handlePowerStateChange();
synchronized (mSimulationWaitObject) {
mWakeFromSimulatedSleep = true;
mSimulationWaitObject.notify();
}
}
/**
* Manually enter simulated suspend (Deep Sleep) mode, trigging Garage mode.
* If the parameter is 'true', reboot the system when Garage Mode completes.
*
* Invoked using "adb shell dumpsys activity service com.android.car suspend" or
* "adb shell dumpsys activity service com.android.car garage-mode reboot".
* This is similar to 'onApPowerStateChange()' except that it needs to create a CpmsState
* that is not directly derived from a VehicleApPowerStateReq.
*/
public void forceSuspendAndMaybeReboot(boolean shouldReboot) {
synchronized (mSimulationWaitObject) {
mInSimulatedDeepSleepMode = true;
mWakeFromSimulatedSleep = false;
mGarageModeShouldExitImmediately = false;
}
PowerHandler handler;
synchronized (mLock) {
mRebootAfterGarageMode = shouldReboot;
mPendingPowerStates.addFirst(new CpmsState(CpmsState.SIMULATE_SLEEP,
CarPowerStateListener.SHUTDOWN_PREPARE));
handler = mHandler;
}
handler.handlePowerStateChange();
}
/**
* Manually defines a power policy.
*
* <p>If the given ID already exists or specified power components are invalid, it fails.
*
* @return {@code true}, if successful. Otherwise, {@code false}.
*/
public boolean definePowerPolicyFromCommand(String[] args, IndentingPrintWriter writer) {
if (args.length < 2) {
writer.println("Too few arguments");
return false;
}
String powerPolicyId = args[1];
int index = 2;
String[] enabledComponents = new String[0];
String[] disabledComponents = new String[0];
while (index < args.length) {
switch (args[index]) {
case "--enable":
if (index == args.length - 1) {
writer.println("No components for --enable");
return false;
}
enabledComponents = args[index + 1].split(",");
break;
case "--disable":
if (index == args.length - 1) {
writer.println("No components for --disabled");
return false;
}
disabledComponents = args[index + 1].split(",");
break;
default:
writer.printf("Unrecognized argument: %s\n", args[index]);
return false;
}
index += 2;
}
int status = definePowerPolicy(powerPolicyId, enabledComponents, disabledComponents);
if (status != PolicyOperationStatus.OK) {
writer.println(PolicyOperationStatus.errorCodeToString(status));
return false;
}
writer.printf("Power policy(%s) is successfully defined.\n", powerPolicyId);
return true;
}
/**
* Defines a power policy with the given id and components.
*
* <p> A policy defined with this method is valid until the system is rebooted/restarted.
*/
@VisibleForTesting
@PolicyOperationStatus.ErrorCode
public int definePowerPolicy(String powerPolicyId, String[] enabledComponents,
String[] disabledComponents) {
int status = mPolicyReader.definePowerPolicy(powerPolicyId,
enabledComponents, disabledComponents);
if (status != PolicyOperationStatus.OK) {
int error = PolicyOperationStatus.ERROR_DEFINE_POWER_POLICY;
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error));
return error;
}
ICarPowerPolicySystemNotification daemon;
synchronized (mLock) {
daemon = mCarPowerPolicyDaemon;
}
try {
daemon.notifyPowerPolicyDefinition(powerPolicyId, enabledComponents,
disabledComponents);
} catch (RemoteException e) {
int error = PolicyOperationStatus.ERROR_DEFINE_POWER_POLICY;
Slogf.w(TAG, PolicyOperationStatus.errorCodeToString(error));
return error;
}
return PolicyOperationStatus.OK;
}
/**
* Manually applies a power policy.
*
* <p>If the given ID is not defined, it fails.
*
* @return {@code true}, if successful. Otherwise, {@code false}.
*/
public boolean applyPowerPolicyFromCommand(String[] args, IndentingPrintWriter writer) {
if (args.length != 2) {
writer.println("Power policy ID should be given");
return false;
}
String powerPolicyId = args[1];
if (powerPolicyId == null) {
writer.println("Policy ID cannot be null");
return false;
}
boolean isPreemptive = mPolicyReader.isPreemptivePowerPolicy(powerPolicyId);
int status = isPreemptive ? applyPreemptivePowerPolicy(powerPolicyId)
: applyPowerPolicy(powerPolicyId, /* upToDaemon= */ true, /* force= */ false);
if (status != PolicyOperationStatus.OK) {
writer.println(PolicyOperationStatus.errorCodeToString(status));
return false;
}
writer.printf("Power policy(%s) is successfully applied.\n", powerPolicyId);
return true;
}
/**
* Manually defines a power policy group.
*
* <p>If the given ID already exists, a wrong power state is given, or specified power policy ID
* doesn't exist, it fails.
*
* @return {@code true}, if successful. Otherwise, {@code false}.
*/
public boolean definePowerPolicyGroupFromCommand(String[] args, IndentingPrintWriter writer) {
if (args.length < 3 || args.length > 4) {
writer.println("Invalid syntax");
return false;
}
String policyGroupId = args[1];
int index = 2;
SparseArray<String> defaultPolicyPerState = new SparseArray<>();
while (index < args.length) {
String[] tokens = args[index].split(":");
if (tokens.length != 2) {
writer.println("Invalid syntax");
return false;
}
int state = PolicyReader.toPowerState(tokens[0]);
if (state == PolicyReader.INVALID_POWER_STATE) {
writer.printf("Invalid power state: %s\n", tokens[0]);
return false;
}
defaultPolicyPerState.put(state, tokens[1]);
index++;
}
int status = mPolicyReader.definePowerPolicyGroup(policyGroupId,
defaultPolicyPerState);
if (status != PolicyOperationStatus.OK) {
writer.println(PolicyOperationStatus.errorCodeToString(status));
return false;
}
writer.printf("Power policy group(%s) is successfully defined.\n", policyGroupId);
return true;
}
/**
* Manually sets a power policy group.
*
* <p>If the given ID is not defined, it fails.
*
* @return {@code true}, if successful. Otherwise, {@code false}.
*/
public boolean setPowerPolicyGroupFromCommand(String[] args, IndentingPrintWriter writer) {
if (args.length != 2) {
writer.println("Power policy group ID should be given");
return false;
}
String policyGroupId = args[1];
int status = setCurrentPowerPolicyGroup(policyGroupId);
if (status != PolicyOperationStatus.OK) {
writer.println(PolicyOperationStatus.errorCodeToString(status));
return false;
}
writer.printf("Setting power policy group(%s) is successful.\n", policyGroupId);
return true;
}
/**
* Powers off the device.
*/
public void powerOffFromCommand(boolean skipGarageMode, boolean reboot) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
Slogf.i(TAG, "%s %s Garage Mode", reboot ? "Rebooting" : "Powering off",
skipGarageMode ? "with" : "without");
int param = skipGarageMode ? VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY
: VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY;
PowerState state = new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE, param);
synchronized (mLock) {
mRebootAfterGarageMode = reboot;
mPendingPowerStates.addFirst(new CpmsState(state));
mLock.notify();
}
mHandler.handlePowerStateChange();
}
/**
* Changes Silent Mode to the given mode.
*/
public void setSilentMode(String silentMode) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
mSilentModeHandler.setSilentMode(silentMode);
}
/**
* Dumps the current Silent Mode.
*/
public void dumpSilentMode(IndentingPrintWriter writer) {
mSilentModeHandler.dump(writer);
}
// In a real Deep Sleep, the hardware removes power from the CPU (but retains power
// on the RAM). This puts the processor to sleep. Upon some external signal, power
// is re-applied to the CPU, and processing resumes right where it left off.
// We simulate this behavior by calling wait().
// We continue from wait() when forceSimulatedResume() is called.
private void simulateSleepByWaiting() {
Slogf.i(TAG, "Starting to simulate Deep Sleep by waiting");
synchronized (mSimulationWaitObject) {
while (!mWakeFromSimulatedSleep) {
try {
mSimulationWaitObject.wait();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt(); // Restore interrupted status
}
}
mInSimulatedDeepSleepMode = false;
}
Slogf.i(TAG, "Exit Deep Sleep simulation");
}
private int getMaxSuspendWaitDurationConfig() {
return mContext.getResources().getInteger(R.integer.config_maxSuspendWaitDuration);
}
private boolean getWifiAdjustmentForSuspendConfig() {
return mContext.getResources().getBoolean(R.bool.config_wifiAdjustmentForSuspend);
}
}