blob: c38041517ad2ef61b822902a5f0c7e80b87c3a8a [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.watchdog;
import static android.car.PlatformVersion.VERSION_CODES;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_REBOOT;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.ACTION_USER_REMOVED;
import static com.android.car.CarLog.TAG_WATCHDOG;
import static com.android.car.CarServiceUtils.assertAnyPermission;
import static com.android.car.CarServiceUtils.assertPermission;
import static com.android.car.CarServiceUtils.isEventAnyOfTypes;
import static com.android.car.CarServiceUtils.runOnMain;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import static com.android.car.internal.NotificationHelperBase.CAR_WATCHDOG_ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION;
import static com.android.car.internal.NotificationHelperBase.CAR_WATCHDOG_ACTION_LAUNCH_APP_SETTINGS;
import static com.android.car.internal.NotificationHelperBase.CAR_WATCHDOG_ACTION_RESOURCE_OVERUSE_DISABLE_APP;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.automotive.watchdog.internal.GarageMode;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
import android.automotive.watchdog.internal.PackageInfo;
import android.automotive.watchdog.internal.PackageIoOveruseStats;
import android.automotive.watchdog.internal.PowerCycle;
import android.automotive.watchdog.internal.ResourceStats;
import android.automotive.watchdog.internal.StateType;
import android.automotive.watchdog.internal.UserPackageIoUsageStats;
import android.automotive.watchdog.internal.UserState;
import android.car.Car;
import android.car.builtin.util.Slogf;
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.CarPowerPolicy;
import android.car.hardware.power.CarPowerPolicyFilter;
import android.car.hardware.power.ICarPowerPolicyListener;
import android.car.hardware.power.ICarPowerStateListener;
import android.car.hardware.power.PowerComponent;
import android.car.user.UserLifecycleEventFilter;
import android.car.watchdog.CarWatchdogManager;
import android.car.watchdog.ICarWatchdogService;
import android.car.watchdog.ICarWatchdogServiceCallback;
import android.car.watchdog.IResourceOveruseListener;
import android.car.watchdog.PackageKillableState;
import android.car.watchdog.ResourceOveruseConfiguration;
import android.car.watchdog.ResourceOveruseStats;
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.car.CarLocalServices;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.ArrayUtils;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.power.CarPowerManagementService;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.lang.ref.WeakReference;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Service to implement CarWatchdogManager API.
*/
public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
static final String TAG = CarLog.tagFor(CarWatchdogService.class);
static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
static final String ACTION_GARAGE_MODE_ON =
"com.android.server.jobscheduler.GARAGE_MODE_ON";
static final String ACTION_GARAGE_MODE_OFF =
"com.android.server.jobscheduler.GARAGE_MODE_OFF";
@VisibleForTesting
static final int MISSING_ARG_VALUE = -1;
private static final String FALLBACK_DATA_SYSTEM_CAR_DIR_PATH = "/data/system/car";
private static final String WATCHDOG_DIR_NAME = "watchdog";
private static final TimeSource SYSTEM_INSTANCE = new TimeSource() {
@Override
public Instant now() {
return Instant.now();
}
@Override
public String toString() {
return "System time instance";
}
};
private final Context mContext;
private final ICarWatchdogServiceForSystemImpl mWatchdogServiceForSystem;
private final PackageInfoHandler mPackageInfoHandler;
private final WatchdogStorage mWatchdogStorage;
private final WatchdogProcessHandler mWatchdogProcessHandler;
private final WatchdogPerfHandler mWatchdogPerfHandler;
private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener;
private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
/*
* TODO(b/192481350): Listen for GarageMode change notification rather than depending on the
* system_server broadcast when the CarService internal API for listening GarageMode change is
* implemented.
*/
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case CAR_WATCHDOG_ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION:
case CAR_WATCHDOG_ACTION_LAUNCH_APP_SETTINGS:
case CAR_WATCHDOG_ACTION_RESOURCE_OVERUSE_DISABLE_APP:
mWatchdogPerfHandler.processUserNotificationIntent(intent);
break;
case ACTION_GARAGE_MODE_ON:
case ACTION_GARAGE_MODE_OFF:
int garageMode;
synchronized (mLock) {
garageMode = mCurrentGarageMode = Objects.equals(action,
ACTION_GARAGE_MODE_ON)
? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF;
}
mWatchdogPerfHandler.onGarageModeChange(garageMode);
if (garageMode == GarageMode.GARAGE_MODE_ON) {
mWatchdogStorage.shrinkDatabase();
}
notifyGarageModeChange(garageMode);
break;
case ACTION_REBOOT:
case ACTION_SHUTDOWN:
// FLAG_RECEIVER_FOREGROUND is checked to ignore the intent from UserController
// when a user is stopped.
if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) == 0) {
break;
}
int powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
powerCycle, /* arg2= */ 0);
if (DEBUG) {
Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)",
powerCycle);
}
} catch (Exception e) {
Slogf.w(TAG, e, "Notifying power cycle state change failed");
}
break;
case ACTION_USER_REMOVED: {
UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
int userId = userHandle.getIdentifier();
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE,
userId, UserState.USER_STATE_REMOVED);
if (DEBUG) {
Slogf.d(TAG, "Notified car watchdog daemon of removed user %d",
userId);
}
} catch (RemoteException e) {
Slogf.w(TAG, e, "Failed to notify car watchdog daemon of removed user %d",
userId);
}
mWatchdogPerfHandler.deleteUser(userId);
break;
}
case ACTION_PACKAGE_CHANGED: {
mWatchdogPerfHandler.processPackageChangedIntent(intent);
break;
}
default:
Slogf.i(TAG, "Ignoring unknown intent %s", intent);
}
}
};
private final ICarPowerStateListener mCarPowerStateListener =
new ICarPowerStateListener.Stub() {
@Override
public void onStateChanged(int state, long expirationTimeMs) {
CarPowerManagementService powerService =
CarLocalServices.getService(CarPowerManagementService.class);
if (powerService == null) {
return;
}
int powerCycle = carPowerStateToPowerCycle(powerService.getPowerState());
switch (powerCycle) {
case PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE:
// Perform time consuming disk I/O operation during shutdown prepare to avoid
// incomplete I/O.
mWatchdogPerfHandler.writeMetadataFile();
break;
case PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER:
// Watchdog service and daemon performs garage mode monitoring so delay writing
// to database until after shutdown enter.
mWatchdogPerfHandler.writeToDatabase();
break;
case PowerCycle.POWER_CYCLE_SUSPEND_EXIT:
break;
// ON covers resume.
case PowerCycle.POWER_CYCLE_RESUME:
// There might be outdated & incorrect info. We should reset them before
// starting to do health check.
mWatchdogProcessHandler.prepareHealthCheck();
break;
default:
return;
}
notifyPowerCycleChange(powerCycle);
}
};
private final ICarPowerPolicyListener mCarDisplayPowerPolicyListener =
new ICarPowerPolicyListener.Stub() {
@Override
public void onPolicyChanged(CarPowerPolicy appliedPolicy,
CarPowerPolicy accumulatedPolicy) {
boolean isDisplayEnabled =
appliedPolicy.isComponentEnabled(PowerComponent.DISPLAY);
boolean didStateChange = false;
synchronized (mLock) {
didStateChange = mIsDisplayEnabled != isDisplayEnabled;
mIsDisplayEnabled = isDisplayEnabled;
}
if (didStateChange) {
mWatchdogPerfHandler.onDisplayStateChanged(isDisplayEnabled);
}
}
};
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mReadyToRespond;
@GuardedBy("mLock")
private boolean mIsConnected;
@GuardedBy("mLock")
private @GarageMode int mCurrentGarageMode;
@GuardedBy("mLock")
private boolean mIsDisplayEnabled;
public CarWatchdogService(Context context, Context carServiceBuiltinPackageContext) {
this(context, carServiceBuiltinPackageContext,
new WatchdogStorage(context, SYSTEM_INSTANCE), SYSTEM_INSTANCE);
}
@VisibleForTesting
public CarWatchdogService(Context context, Context carServiceBuiltinPackageContext,
WatchdogStorage watchdogStorage, TimeSource timeSource) {
this(context, carServiceBuiltinPackageContext, watchdogStorage,
timeSource, /*watchdogProcessHandler=*/ null, /*watchdogPerfHandler=*/ null);
}
@VisibleForTesting
CarWatchdogService(Context context, Context carServiceBuiltinPackageContext,
WatchdogStorage watchdogStorage, TimeSource timeSource,
WatchdogProcessHandler watchdogProcessHandler,
WatchdogPerfHandler watchdogPerfHandler) {
mContext = context;
mWatchdogStorage = watchdogStorage;
mPackageInfoHandler = new PackageInfoHandler(mContext.getPackageManager());
mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper(TAG_WATCHDOG);
mWatchdogServiceForSystem = new ICarWatchdogServiceForSystemImpl(this);
mWatchdogProcessHandler = watchdogProcessHandler != null ? watchdogProcessHandler
: new WatchdogProcessHandler(mWatchdogServiceForSystem, mCarWatchdogDaemonHelper);
mWatchdogPerfHandler =
watchdogPerfHandler != null ? watchdogPerfHandler : new WatchdogPerfHandler(
mContext, carServiceBuiltinPackageContext,
mCarWatchdogDaemonHelper, mPackageInfoHandler, mWatchdogStorage,
timeSource);
mConnectionListener = (isConnected) -> {
mWatchdogPerfHandler.onDaemonConnectionChange(isConnected);
synchronized (mLock) {
mIsConnected = isConnected;
}
registerToDaemon();
};
mCurrentGarageMode = GarageMode.GARAGE_MODE_OFF;
mIsDisplayEnabled = true;
}
@VisibleForTesting
public void setCarWatchdogDaemonHelper(CarWatchdogDaemonHelper helper) {
mCarWatchdogDaemonHelper = helper;
}
@Override
public void init() {
// TODO(b/266008677): The daemon reads the sendResourceUsageStatsEnabled sysprop at the
// moment the CarWatchdogService connects to it. Therefore, the property must be set by
// CarWatchdogService before connecting with the CarWatchdog daemon. Set the property to
// true to enable the sending of resource usage stats from the daemon.
mWatchdogProcessHandler.init();
mWatchdogPerfHandler.init();
subscribePowerManagementService();
subscribeUserStateChange();
subscribeBroadcastReceiver();
mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
mCarWatchdogDaemonHelper.connect();
// To make sure the main handler is ready for responding to car watchdog daemon, registering
// to the daemon is done through the main handler. Once the registration is completed, we
// can assume that the main handler is not too busy handling other stuffs.
postRegisterToDaemonMessage();
if (DEBUG) {
Slogf.d(TAG, "CarWatchdogService is initialized");
}
}
@Override
public void release() {
mContext.unregisterReceiver(mBroadcastReceiver);
unsubscribePowerManagementService();
mWatchdogPerfHandler.release();
mWatchdogStorage.release();
unregisterFromDaemon();
mCarWatchdogDaemonHelper.disconnect();
}
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dump(IndentingPrintWriter writer) {
writer.println("*" + getClass().getSimpleName() + "*");
writer.increaseIndent();
synchronized (mLock) {
writer.println("Current garage mode: " + toGarageModeString(mCurrentGarageMode));
}
mWatchdogProcessHandler.dump(writer);
mWatchdogPerfHandler.dump(writer);
writer.decreaseIndent();
}
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dumpProto(ProtoOutputStream proto) {}
/**
* Registers {@link android.car.watchdog.ICarWatchdogServiceCallback} to
* {@link CarWatchdogService}.
*/
@Override
public void registerClient(ICarWatchdogServiceCallback client, int timeout) {
assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
mWatchdogProcessHandler.registerClient(client, timeout);
}
/**
* Unregisters {@link android.car.watchdog.ICarWatchdogServiceCallback} from
* {@link CarWatchdogService}.
*/
@Override
public void unregisterClient(ICarWatchdogServiceCallback client) {
assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
mWatchdogProcessHandler.unregisterClient(client);
}
/**
* Tells {@link CarWatchdogService} that the client is alive.
*/
@Override
public void tellClientAlive(ICarWatchdogServiceCallback client, int sessionId) {
assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
mWatchdogProcessHandler.tellClientAlive(client, sessionId);
}
/** Returns {@link android.car.watchdog.ResourceOveruseStats} for the calling package. */
@Override
@NonNull
public ResourceOveruseStats getResourceOveruseStats(
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
@CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
return mWatchdogPerfHandler.getResourceOveruseStats(resourceOveruseFlag, maxStatsPeriod);
}
/**
* Returns {@link android.car.watchdog.ResourceOveruseStats} for all packages for the maximum
* specified period, and the specified resource types with stats greater than or equal to the
* minimum specified stats.
*/
@Override
@NonNull
public List<ResourceOveruseStats> getAllResourceOveruseStats(
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
@CarWatchdogManager.MinimumStatsFlag int minimumStatsFlag,
@CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
return mWatchdogPerfHandler.getAllResourceOveruseStats(resourceOveruseFlag,
minimumStatsFlag, maxStatsPeriod);
}
/** Returns {@link android.car.watchdog.ResourceOveruseStats} for the specified user package. */
@Override
@NonNull
public ResourceOveruseStats getResourceOveruseStatsForUserPackage(
@NonNull String packageName, @NonNull UserHandle userHandle,
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
@CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
return mWatchdogPerfHandler.getResourceOveruseStatsForUserPackage(packageName, userHandle,
resourceOveruseFlag, maxStatsPeriod);
}
/**
* Adds {@link android.car.watchdog.IResourceOveruseListener} for the calling package's resource
* overuse notifications.
*/
@Override
public void addResourceOveruseListener(
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
@NonNull IResourceOveruseListener listener) {
mWatchdogPerfHandler.addResourceOveruseListener(resourceOveruseFlag, listener);
}
/**
* Removes the previously added {@link android.car.watchdog.IResourceOveruseListener} for the
* calling package's resource overuse notifications.
*/
@Override
public void removeResourceOveruseListener(@NonNull IResourceOveruseListener listener) {
mWatchdogPerfHandler.removeResourceOveruseListener(listener);
}
/**
* Adds {@link android.car.watchdog.IResourceOveruseListener} for all packages' resource overuse
* notifications.
*/
@Override
public void addResourceOveruseListenerForSystem(
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
@NonNull IResourceOveruseListener listener) {
assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
mWatchdogPerfHandler.addResourceOveruseListenerForSystem(resourceOveruseFlag, listener);
}
/**
* Removes the previously added {@link android.car.watchdog.IResourceOveruseListener} for all
* packages' resource overuse notifications.
*/
@Override
public void removeResourceOveruseListenerForSystem(@NonNull IResourceOveruseListener listener) {
assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
mWatchdogPerfHandler.removeResourceOveruseListenerForSystem(listener);
}
/** Sets whether or not a user package is killable on resource overuse. */
@Override
public void setKillablePackageAsUser(String packageName, UserHandle userHandle,
boolean isKillable) {
assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
mWatchdogPerfHandler.setKillablePackageAsUser(packageName, userHandle, isKillable);
}
/**
* Returns all {@link android.car.watchdog.PackageKillableState} on resource overuse for
* the specified user.
*/
@Override
@NonNull
public List<PackageKillableState> getPackageKillableStatesAsUser(UserHandle userHandle) {
assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
return mWatchdogPerfHandler.getPackageKillableStatesAsUser(userHandle);
}
/**
* Sets {@link android.car.watchdog.ResourceOveruseConfiguration} for the specified resources.
*/
@Override
@CarWatchdogManager.ReturnCode
public int setResourceOveruseConfigurations(
List<ResourceOveruseConfiguration> configurations,
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag)
throws RemoteException {
assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
return mWatchdogPerfHandler.setResourceOveruseConfigurations(configurations,
resourceOveruseFlag);
}
/** Returns the available {@link android.car.watchdog.ResourceOveruseConfiguration}. */
@Override
@NonNull
public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations(
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
assertAnyPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG,
Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
return mWatchdogPerfHandler.getResourceOveruseConfigurations(resourceOveruseFlag);
}
/**
* Enables/disables the watchdog daemon client health check process.
*/
public void controlProcessHealthCheck(boolean enable) {
assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
mWatchdogProcessHandler.controlProcessHealthCheck(enable);
}
/**
* Kills a specific package for a user due to resource overuse.
*
* @return whether package was killed
*/
public boolean performResourceOveruseKill(String packageName, @UserIdInt int userId) {
assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
return mWatchdogPerfHandler.disablePackageForUser(packageName, userId);
}
/**
* Sets the thread priority for a specific thread.
*
* The thread must belong to the calling process.
*
* @throws IllegalArgumentException If the policy/priority is not valid.
* @throws IllegalStateException If the provided tid does not belong to the calling process.
* @throws RemoteException If binder error happens.
* @throws ServiceSpecificException If car watchdog daemon failed to set the thread priority.
* @throws UnsupportedOperationException If the current android release doesn't support the API.
*/
public void setThreadPriority(int pid, int tid, int uid, int policy, int priority)
throws RemoteException {
mCarWatchdogDaemonHelper.setThreadPriority(pid, tid, uid, policy, priority);
}
/**
* Gets the thread scheduling policy and priority for the specified thread.
*
* The thread must belong to the calling process.
*
* @throws IllegalStateException If the provided tid does not belong to the calling process or
* car watchdog daemon failed to get the priority.
* @throws RemoteException If binder error happens.
* @throws UnsupportedOperationException If the current android release doesn't support the API.
*/
public int[] getThreadPriority(int pid, int tid, int uid) throws RemoteException {
try {
return mCarWatchdogDaemonHelper.getThreadPriority(pid, tid, uid);
} catch (ServiceSpecificException e) {
// Car watchdog daemon failed to get the priority.
throw new IllegalStateException(e);
}
}
@VisibleForTesting
int getClientCount(int timeout) {
return mWatchdogProcessHandler.getClientCount(timeout);
}
@VisibleForTesting
void setOveruseHandlingDelay(long millis) {
mWatchdogPerfHandler.setOveruseHandlingDelay(millis);
}
static File getWatchdogDirFile() {
SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
String systemCarDirPath = systemInterface == null ? FALLBACK_DATA_SYSTEM_CAR_DIR_PATH
: systemInterface.getSystemCarDir().getAbsolutePath();
return new File(systemCarDirPath, WATCHDOG_DIR_NAME);
}
private void notifyAllUserStates() {
UserManager userManager = mContext.getSystemService(UserManager.class);
List<UserHandle> users = userManager.getUserHandles(/* excludeDying= */ false);
try {
// TODO(b/152780162): reduce the number of RPC calls(isUserRunning).
for (int i = 0; i < users.size(); ++i) {
UserHandle user = users.get(i);
int userState = userManager.isUserRunning(user)
? UserState.USER_STATE_STARTED
: UserState.USER_STATE_STOPPED;
mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE,
user.getIdentifier(), userState);
mWatchdogProcessHandler.updateUserState(user.getIdentifier(),
userState == UserState.USER_STATE_STOPPED);
}
if (DEBUG) {
Slogf.d(TAG, "Notified car watchdog daemon of user states");
}
} catch (RemoteException | RuntimeException e) {
// When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Notifying latest user states failed");
}
}
private void notifyPowerCycleChange(@PowerCycle int powerCycle) {
// TODO(b/236876940): Change version check to TIRAMISU_2 when cherry picking to T-QPR2.
if (!Car.getPlatformVersion().isAtLeast(VERSION_CODES.UPSIDE_DOWN_CAKE_0)
&& powerCycle == PowerCycle.POWER_CYCLE_SUSPEND_EXIT) {
return;
}
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(
StateType.POWER_CYCLE, powerCycle, MISSING_ARG_VALUE);
if (DEBUG) {
Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
}
} catch (RemoteException | RuntimeException e) {
// When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Notifying power cycle change to %d failed", powerCycle);
}
}
private void notifyGarageModeChange(@GarageMode int garageMode) {
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(
StateType.GARAGE_MODE, garageMode, MISSING_ARG_VALUE);
if (DEBUG) {
Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%d)", garageMode);
}
} catch (RemoteException | RuntimeException e) {
// When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Notifying garage mode change to %d failed", garageMode);
}
}
private void postRegisterToDaemonMessage() {
runOnMain(() -> {
synchronized (mLock) {
mReadyToRespond = true;
}
registerToDaemon();
});
}
private void registerToDaemon() {
synchronized (mLock) {
if (!mIsConnected || !mReadyToRespond) {
return;
}
}
try {
mCarWatchdogDaemonHelper.registerCarWatchdogService(mWatchdogServiceForSystem);
if (DEBUG) {
Slogf.d(TAG, "CarWatchdogService registers to car watchdog daemon");
}
} catch (RemoteException | RuntimeException e) {
// When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Cannot register to car watchdog daemon");
}
notifyAllUserStates();
CarPowerManagementService powerService =
CarLocalServices.getService(CarPowerManagementService.class);
if (powerService != null) {
int powerState = powerService.getPowerState();
int powerCycle = carPowerStateToPowerCycle(powerState);
if (powerCycle >= 0) {
notifyPowerCycleChange(powerCycle);
} else {
Slogf.i(TAG, "Skipping notifying %d power state", powerState);
}
}
int garageMode;
synchronized (mLock) {
// To avoid race condition, fetch {@link mCurrentGarageMode} just before
// the {@link notifyGarageModeChange} call. For instance, if {@code mCurrentGarageMode}
// changes before the above {@link notifyPowerCycleChange} call returns,
// the {@link garageMode}'s value will be out of date.
garageMode = mCurrentGarageMode;
}
notifyGarageModeChange(garageMode);
}
private void unregisterFromDaemon() {
try {
mCarWatchdogDaemonHelper.unregisterCarWatchdogService(mWatchdogServiceForSystem);
if (DEBUG) {
Slogf.d(TAG, "CarWatchdogService unregisters from car watchdog daemon");
}
} catch (RemoteException | RuntimeException e) {
// When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Cannot unregister from car watchdog daemon");
}
}
private void subscribePowerManagementService() {
CarPowerManagementService powerService =
CarLocalServices.getService(CarPowerManagementService.class);
if (powerService == null) {
Slogf.w(TAG, "Cannot get CarPowerManagementService");
return;
}
powerService.registerListener(mCarPowerStateListener);
powerService.addPowerPolicyListener(
new CarPowerPolicyFilter.Builder().setComponents(PowerComponent.DISPLAY).build(),
mCarDisplayPowerPolicyListener);
}
private void unsubscribePowerManagementService() {
CarPowerManagementService powerService =
CarLocalServices.getService(CarPowerManagementService.class);
if (powerService == null) {
Slogf.w(TAG, "Cannot get CarPowerManagementService");
return;
}
powerService.unregisterListener(mCarPowerStateListener);
powerService.removePowerPolicyListener(mCarDisplayPowerPolicyListener);
}
private void subscribeUserStateChange() {
CarUserService userService = CarLocalServices.getService(CarUserService.class);
if (userService == null) {
Slogf.w(TAG, "Cannot get CarUserService");
return;
}
UserLifecycleEventFilter userEventFilter =
new UserLifecycleEventFilter.Builder()
.addEventType(USER_LIFECYCLE_EVENT_TYPE_STARTING)
.addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
.addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)
.addEventType(USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED)
.addEventType(USER_LIFECYCLE_EVENT_TYPE_STOPPED).build();
userService.addUserLifecycleListener(userEventFilter, (event) -> {
if (!isEventAnyOfTypes(TAG, event, USER_LIFECYCLE_EVENT_TYPE_STARTING,
USER_LIFECYCLE_EVENT_TYPE_SWITCHING, USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) {
return;
}
if (!Car.getPlatformVersion().isAtLeast(VERSION_CODES.TIRAMISU_1)
&& !isEventAnyOfTypes(TAG, event,
USER_LIFECYCLE_EVENT_TYPE_STARTING, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) {
return;
}
int userId = event.getUserHandle().getIdentifier();
int userState;
String userStateDesc;
switch (event.getEventType()) {
case USER_LIFECYCLE_EVENT_TYPE_STARTING:
mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/ false);
userState = UserState.USER_STATE_STARTED;
userStateDesc = "STARTING";
break;
case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
userState = UserState.USER_STATE_SWITCHING;
userStateDesc = "SWITCHING";
break;
case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING:
userState = UserState.USER_STATE_UNLOCKING;
userStateDesc = "UNLOCKING";
break;
case USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED:
userState = UserState.USER_STATE_POST_UNLOCKED;
userStateDesc = "POST_UNLOCKED";
break;
case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/ true);
userState = UserState.USER_STATE_STOPPED;
userStateDesc = "STOPPING";
break;
default:
return;
}
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE, userId,
userState);
if (DEBUG) {
Slogf.d(TAG, "Notified car watchdog daemon user %d's user state, %s",
userId, userStateDesc);
}
} catch (RemoteException | RuntimeException e) {
// When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Notifying user state change failed");
}
});
}
private void subscribeBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(CAR_WATCHDOG_ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION);
filter.addAction(ACTION_GARAGE_MODE_ON);
filter.addAction(ACTION_GARAGE_MODE_OFF);
filter.addAction(CAR_WATCHDOG_ACTION_LAUNCH_APP_SETTINGS);
filter.addAction(CAR_WATCHDOG_ACTION_RESOURCE_OVERUSE_DISABLE_APP);
filter.addAction(ACTION_USER_REMOVED);
filter.addAction(ACTION_REBOOT);
filter.addAction(ACTION_SHUTDOWN);
mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter,
Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG, /* scheduler= */ null,
Context.RECEIVER_NOT_EXPORTED);
// The package data scheme applies only for the ACTION_PACKAGE_CHANGED action. So, add a
// filter for this action separately. Otherwise, the broadcast receiver won't receive
// notifications for other actions.
IntentFilter packageChangedFilter = new IntentFilter();
packageChangedFilter.addAction(ACTION_PACKAGE_CHANGED);
packageChangedFilter.addDataScheme("package");
mContext.registerReceiverForAllUsers(mBroadcastReceiver, packageChangedFilter,
/* broadcastPermission= */ null, /* scheduler= */ null,
Context.RECEIVER_NOT_EXPORTED);
}
private static int carPowerStateToPowerCycle(int powerState) {
switch (powerState) {
// SHUTDOWN_PREPARE covers suspend and shutdown.
case CarPowerManager.STATE_SHUTDOWN_PREPARE:
return PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE;
case CarPowerManager.STATE_SHUTDOWN_ENTER:
case CarPowerManager.STATE_SUSPEND_ENTER:
case CarPowerManager.STATE_HIBERNATION_ENTER:
return PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
case CarPowerManager.STATE_SUSPEND_EXIT:
case CarPowerManager.STATE_HIBERNATION_EXIT:
return PowerCycle.POWER_CYCLE_SUSPEND_EXIT;
// ON covers resume.
case CarPowerManager.STATE_ON:
return PowerCycle.POWER_CYCLE_RESUME;
default:
Slogf.e(TAG, "Invalid power state: %d", powerState);
}
return -1;
}
private static String toGarageModeString(@GarageMode int garageMode) {
switch (garageMode) {
case GarageMode.GARAGE_MODE_OFF:
return "GARAGE_MODE_OFF";
case GarageMode.GARAGE_MODE_ON:
return "GARAGE_MODE_ON";
default:
Slogf.e(TAG, "Invalid garage mode: %d", garageMode);
}
return "INVALID";
}
private static final class ICarWatchdogServiceForSystemImpl
extends ICarWatchdogServiceForSystem.Stub {
private final WeakReference<CarWatchdogService> mService;
ICarWatchdogServiceForSystemImpl(CarWatchdogService service) {
mService = new WeakReference<>(service);
}
@Override
public void checkIfAlive(int sessionId, int timeout) {
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return;
}
service.mWatchdogProcessHandler.postHealthCheckMessage(sessionId);
}
@Override
public void prepareProcessTermination() {
Slogf.w(TAG, "CarWatchdogService is about to be killed by car watchdog daemon");
}
@Override
public List<PackageInfo> getPackageInfosForUids(
int[] uids, List<String> vendorPackagePrefixes) {
if (ArrayUtils.isEmpty(uids)) {
Slogf.w(TAG, "UID list is empty");
return Collections.emptyList();
}
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return Collections.emptyList();
}
return service.mPackageInfoHandler.getPackageInfosForUids(uids, vendorPackagePrefixes);
}
// TODO(b/269191275): This method was replaced by onLatestResourceStats in Android U.
// Make method no-op in Android W (N+2 releases).
@Override
public void latestIoOveruseStats(List<PackageIoOveruseStats> packageIoOveruseStats) {
if (packageIoOveruseStats.isEmpty()) {
Slogf.w(TAG, "Latest I/O overuse stats is empty");
return;
}
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return;
}
service.mWatchdogPerfHandler.latestIoOveruseStats(packageIoOveruseStats);
}
@Override
public void onLatestResourceStats(List<ResourceStats> resourceStats) {
// TODO(b/266008146): Handle the resourceUsageStats.
if (resourceStats.isEmpty()) {
Slogf.w(TAG, "Latest resource stats is empty");
return;
}
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return;
}
for (int i = 0; i < resourceStats.size(); i++) {
ResourceStats stats = resourceStats.get(i);
if (stats.resourceOveruseStats == null
|| stats.resourceOveruseStats.packageIoOveruseStats.isEmpty()) {
Slogf.w(TAG, "Received latest I/O overuse stats is empty");
continue;
}
service.mWatchdogPerfHandler.latestIoOveruseStats(
stats.resourceOveruseStats.packageIoOveruseStats);
}
}
@Override
public void resetResourceOveruseStats(List<String> packageNames) {
if (packageNames.isEmpty()) {
Slogf.w(TAG, "Provided an empty package name to reset resource overuse stats");
return;
}
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return;
}
service.mWatchdogPerfHandler.resetResourceOveruseStats(new ArraySet<>(packageNames));
}
// TODO(b/273354756): This method was replaced by an async request/response pattern
// Android U. Requests for the I/O stats are received through the requestTodayIoUsageStats
// method. And responses are sent through the carwatchdog daemon via
// ICarWatchdog#onTodayIoUsageStats. Make method no-op in Android W (N+2 releases).
@Override
public List<UserPackageIoUsageStats> getTodayIoUsageStats() {
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return Collections.emptyList();
}
return service.mWatchdogPerfHandler.getTodayIoUsageStats();
}
@Override
public void requestAidlVhalPid() {
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return;
}
service.mWatchdogProcessHandler.asyncFetchAidlVhalPid();
}
@Override
public void requestTodayIoUsageStats() {
CarWatchdogService service = mService.get();
if (service == null) {
Slogf.w(TAG, "CarWatchdogService is not available");
return;
}
service.mWatchdogPerfHandler.asyncFetchTodayIoUsageStats();
}
@Override
public String getInterfaceHash() {
return ICarWatchdogServiceForSystemImpl.HASH;
}
@Override
public int getInterfaceVersion() {
return ICarWatchdogServiceForSystemImpl.VERSION;
}
}
}