blob: d80aee132c990f20c0585091996f5e25b7d1e8db [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 android.car.watchdog;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarManagerBase;
import android.car.annotation.AddedInOrBefore;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* CarWatchdogManager allows applications to collect latest system resource overuse statistics, add
* listener for resource overuse notifications, and update resource overuse configurations.
*/
public final class CarWatchdogManager extends CarManagerBase {
private static final String TAG = CarWatchdogManager.class.getSimpleName();
private static final boolean DEBUG = false; // STOPSHIP if true
private static final int INVALID_SESSION_ID = -1;
private static final int NUMBER_OF_CONDITIONS_TO_BE_MET = 2;
private final Runnable mMainThreadCheck = () -> checkMainThread();
/**
* Timeout for services which should be responsive. The length is 3,000 milliseconds.
*
* @hide
*/
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int TIMEOUT_CRITICAL = 0;
/**
* Timeout for services which are relatively responsive. The length is 5,000 milliseconds.
*
* @hide
*/
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int TIMEOUT_MODERATE = 1;
/**
* Timeout for all other services. The length is 10,000 milliseconds.
*
* @hide
*/
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int TIMEOUT_NORMAL = 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "TIMEOUT_", value = {
TIMEOUT_CRITICAL,
TIMEOUT_MODERATE,
TIMEOUT_NORMAL,
})
@Target({ElementType.TYPE_USE})
public @interface TimeoutLengthEnum {}
private final ICarWatchdogService mService;
private final ICarWatchdogClientImpl mClientImpl;
private final IResourceOveruseListenerImpl mResourceOveruseListenerImpl;
private final IResourceOveruseListenerImpl mResourceOveruseListenerForSystemImpl;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SessionInfo mSession = new SessionInfo(INVALID_SESSION_ID, INVALID_SESSION_ID);
@GuardedBy("mLock")
private final List<ResourceOveruseListenerInfo> mResourceOveruseListenerInfos;
@GuardedBy("mLock")
private final List<ResourceOveruseListenerInfo> mResourceOveruseListenerForSystemInfos;
@GuardedBy("mLock")
private CarWatchdogClientCallback mRegisteredClient;
@GuardedBy("mLock")
private Executor mCallbackExecutor;
@GuardedBy("mLock")
private int mRemainingConditions;
/**
* CarWatchdogClientCallback is implemented by the clients which want to be health-checked by
* car watchdog server. Every time onCheckHealthStatus is called, they are expected to
* respond by calling {@link #tellClientAlive} within timeout. If they don't
* respond, car watchdog server reports the current state and kills them.
*
* <p>Before car watchdog server kills the client, it calls onPrepareProcessTermination to allow
* them to prepare the termination. They will be killed in 1 second.
*
* @hide
*/
@SystemApi
public abstract static class CarWatchdogClientCallback {
/**
* Car watchdog server pings the client to check if it is alive.
*
* <p>The callback method is called at the Executor which is specified in {@link
* CarWatchdogManager#registerClient}.
*
* @param sessionId Unique id to distinguish each health checking.
* @param timeout Time duration within which the client should respond.
*
* @return whether the response is immediately acknowledged. If {@code true}, car watchdog
* server considers that the response is acknowledged already. If {@code false},
* the client should call {@link CarWatchdogManager#tellClientAlive} later to tell
* that it is alive.
*/
@AddedInOrBefore(majorVersion = 33)
public boolean onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout) {
return false;
}
/**
* Car watchdog server notifies the client that it will be terminated in 1 second.
*
* <p>The callback method is called at the Executor which is specified in {@link
* CarWatchdogManager#registerClient}.
*/
@AddedInOrBefore(majorVersion = 33)
public void onPrepareProcessTermination() {}
}
/** @hide */
public CarWatchdogManager(Car car, IBinder service) {
super(car);
mService = ICarWatchdogService.Stub.asInterface(service);
mClientImpl = new ICarWatchdogClientImpl(this);
mResourceOveruseListenerImpl = new IResourceOveruseListenerImpl(this, /* isSystem= */false);
mResourceOveruseListenerForSystemImpl = new IResourceOveruseListenerImpl(this,
/* isSystem= */true);
mResourceOveruseListenerInfos = new ArrayList<>();
mResourceOveruseListenerForSystemInfos = new ArrayList<>();
}
/**
* Registers the car watchdog clients to {@link CarWatchdogManager}.
*
* <p>It is allowed to register a client from any thread, but only one client can be
* registered. If two or more clients are needed, create a new {@link Car} and register a client
* to it.
*
* @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
* @param timeout The time duration within which the client desires to respond. The actual
* timeout is decided by watchdog server.
* @throws IllegalStateException if at least one client is already registered.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
@AddedInOrBefore(majorVersion = 33)
public void registerClient(@NonNull @CallbackExecutor Executor executor,
@NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout) {
Objects.requireNonNull(client, "Client must be non-null");
Objects.requireNonNull(executor, "Executor must be non-null");
synchronized (mLock) {
if (mRegisteredClient == client) {
return;
}
if (mRegisteredClient != null) {
throw new IllegalStateException(
"Cannot register the client. Only one client can be registered.");
}
mRegisteredClient = client;
mCallbackExecutor = executor;
}
try {
mService.registerClient(mClientImpl, timeout);
if (DEBUG) {
Log.d(TAG, "Car watchdog client is successfully registered");
}
} catch (RemoteException e) {
synchronized (mLock) {
mRegisteredClient = null;
}
handleRemoteExceptionFromCarService(e);
}
}
/**
* Unregisters the car watchdog client from {@link CarWatchdogManager}.
*
* @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
@AddedInOrBefore(majorVersion = 33)
public void unregisterClient(@NonNull CarWatchdogClientCallback client) {
Objects.requireNonNull(client, "Client must be non-null");
synchronized (mLock) {
if (mRegisteredClient != client) {
Log.w(TAG, "Cannot unregister the client. It has not been registered.");
return;
}
mRegisteredClient = null;
mCallbackExecutor = null;
}
try {
mService.unregisterClient(mClientImpl);
if (DEBUG) {
Log.d(TAG, "Car watchdog client is successfully unregistered");
}
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
/**
* Tells {@link CarWatchdogManager} that the client is alive.
*
* @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
* @param sessionId Session id given by {@link CarWatchdogManager}.
* @throws IllegalStateException if {@code client} is not registered.
* @throws IllegalArgumentException if {@code sessionId} is not correct.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
@AddedInOrBefore(majorVersion = 33)
public void tellClientAlive(@NonNull CarWatchdogClientCallback client, int sessionId) {
Objects.requireNonNull(client, "Client must be non-null");
boolean shouldReport;
synchronized (mLock) {
if (mRegisteredClient != client) {
throw new IllegalStateException(
"Cannot report client status. The client has not been registered.");
}
Preconditions.checkArgument(sessionId != -1 && mSession.currentId == sessionId,
"Cannot report client status. Received session id (" + sessionId
+ ") doesn't match the current one (" + mSession.currentId + ").");
if (mSession.lastReportedId == sessionId) {
Log.w(TAG, "The given session id is already reported.");
return;
}
mSession.lastReportedId = sessionId;
mRemainingConditions--;
shouldReport = checkConditionLocked();
}
if (shouldReport) {
reportToService(sessionId);
}
}
/** @hide */
@IntDef(flag = false, prefix = { "STATS_PERIOD_" }, value = {
STATS_PERIOD_CURRENT_DAY,
STATS_PERIOD_PAST_3_DAYS,
STATS_PERIOD_PAST_7_DAYS,
STATS_PERIOD_PAST_15_DAYS,
STATS_PERIOD_PAST_30_DAYS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface StatsPeriod {}
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_RESOURCE_OVERUSE_" }, value = {
FLAG_RESOURCE_OVERUSE_IO,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResourceOveruseFlag {}
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_MINIMUM_STATS_" }, value = {
FLAG_MINIMUM_STATS_IO_1_MB,
FLAG_MINIMUM_STATS_IO_100_MB,
FLAG_MINIMUM_STATS_IO_1_GB,
})
@Retention(RetentionPolicy.SOURCE)
public @interface MinimumStatsFlag {}
/** @hide */
@IntDef(flag = true, prefix = { "RETURN_CODE_" }, value = {
RETURN_CODE_SUCCESS,
RETURN_CODE_ERROR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReturnCode {}
/**
* Constants that define the stats period in days.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STATS_PERIOD_CURRENT_DAY = 1;
@AddedInOrBefore(majorVersion = 33)
public static final int STATS_PERIOD_PAST_3_DAYS = 2;
@AddedInOrBefore(majorVersion = 33)
public static final int STATS_PERIOD_PAST_7_DAYS = 3;
@AddedInOrBefore(majorVersion = 33)
public static final int STATS_PERIOD_PAST_15_DAYS = 4;
@AddedInOrBefore(majorVersion = 33)
public static final int STATS_PERIOD_PAST_30_DAYS = 5;
/**
* Constants that define the type of resource overuse.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int FLAG_RESOURCE_OVERUSE_IO = 1 << 0;
/**
* Constants that define the minimum stats for each resource type.
*
* Below constants specify the minimum amount of data written to disk.
*
* @hide
*/
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int FLAG_MINIMUM_STATS_IO_1_MB = 1 << 0;
/** @hide */
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int FLAG_MINIMUM_STATS_IO_100_MB = 1 << 1;
/** @hide */
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int FLAG_MINIMUM_STATS_IO_1_GB = 1 << 2;
// Return codes used to indicate the result of a request.
/** @hide */
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int RETURN_CODE_SUCCESS = 0;
/** @hide */
@SystemApi
@AddedInOrBefore(majorVersion = 33)
public static final int RETURN_CODE_ERROR = -1;
/**
* Returns resource overuse stats for the calling package. Returns {@code null}, if no stats.
*
* @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
* @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
*
* @return Resource overuse stats for the calling package. If the calling package has no stats
* for a specified resource overuse type, null value is returned for the corresponding
* resource overuse stats. If the calling package doesn't have sufficient stats for
* {@code maxStatsPeriod} for a specified resource overuse type, the stats are returned
* only for the period returned in the individual resource overuse stats.
*/
@NonNull
@AddedInOrBefore(majorVersion = 33)
public ResourceOveruseStats getResourceOveruseStats(
@ResourceOveruseFlag int resourceOveruseFlag,
@StatsPeriod int maxStatsPeriod) {
try {
return mService.getResourceOveruseStats(resourceOveruseFlag, maxStatsPeriod);
} catch (RemoteException e) {
ResourceOveruseStats.Builder builder =
new ResourceOveruseStats.Builder("", UserHandle.CURRENT);
return handleRemoteExceptionFromCarService(e, builder.build());
}
}
/**
* Returns resource overuse stats for all monitored packages.
*
* @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
* @param minimumStatsFlag Flag to specify the minimum stats for each resource overuse type.
* Only stats above the specified minimum stats for a resource overuse
* type will be returned. May provide only one minimum stats flag for
* each resource overuse type. When no minimum stats flag is specified,
* all stats are returned.
* @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
*
* @return Resource overuse stats for all monitored packages. If any package doesn't have stats
* for a specified resource type, null value is returned for the corresponding resource
* overuse stats. If any package doesn't have sufficient stats for
* {@code maxStatsPeriod} for a specified resource overuse type, the stats are returned
* only for the period returned in the individual resource stats.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
@NonNull
@AddedInOrBefore(majorVersion = 33)
public List<ResourceOveruseStats> getAllResourceOveruseStats(
@ResourceOveruseFlag int resourceOveruseFlag,
@MinimumStatsFlag int minimumStatsFlag,
@StatsPeriod int maxStatsPeriod) {
try {
return mService.getAllResourceOveruseStats(resourceOveruseFlag, minimumStatsFlag,
maxStatsPeriod);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, new ArrayList<>());
}
}
/**
* Returns resource overuse stats for a specific user package.
*
* @param packageName Name of the package whose stats should be returned.
* @param userHandle Handle of the user whose stats should be returned.
* @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
* @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
*
* @return Resource overuse stats for the specified user package. If the user package has no
* stats for a specified resource overuse type, null value is returned for the
* corresponding resource overuse stats. If the user package doesn't have sufficient
* stats for {@code maxStatsPeriod} for a specified resource overuse type, the stats are
* returned only for the period returned in the individual resource overuse stats.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
@NonNull
@AddedInOrBefore(majorVersion = 33)
public ResourceOveruseStats getResourceOveruseStatsForUserPackage(
@NonNull String packageName, @NonNull UserHandle userHandle,
@ResourceOveruseFlag int resourceOveruseFlag,
@StatsPeriod int maxStatsPeriod) {
try {
return mService.getResourceOveruseStatsForUserPackage(packageName, userHandle,
resourceOveruseFlag, maxStatsPeriod);
} catch (RemoteException e) {
ResourceOveruseStats.Builder builder =
new ResourceOveruseStats.Builder("", userHandle);
return handleRemoteExceptionFromCarService(e, builder.build());
}
}
/**
* Listener to get resource overuse notifications.
*
* <p>Applications implement the listener method to take action and/or log on resource overuse.
*/
public interface ResourceOveruseListener {
/**
* Called when a package either overuses a resource or about to overuse a resource.
*
* <p>The listener is called at the executor which is specified in {@link
* CarWatchdogManager#addResourceOveruseListener} or
* {@code addResourceOveruseListenerForSystem}.
*
* <p>The listener is called only on overusing one of the resources specified at the
* {@code resourceOveruseFlag} in {@link CarWatchdogManager#addResourceOveruseListener} or
* {@code addResourceOveruseListenerForSystem}.
*
* @param resourceOveruseStats Resource overuse stats containing stats only for resources
* overuse types that are either overused or about to be
* overused by the package. Implementations must check for null
* value in each resource overuse stats before reading the
* stats.
*/
@AddedInOrBefore(majorVersion = 33)
void onOveruse(@NonNull ResourceOveruseStats resourceOveruseStats);
}
/**
* Adds the {@link ResourceOveruseListener} for the calling package.
*
* <p>Resource overuse notifications are sent only for the calling package's resource overuse.
*
* @param listener Listener implementing {@link ResourceOveruseListener} interface.
* @param resourceOveruseFlag Flag to indicate the types of resource overuses to listen.
*
* @throws IllegalStateException if (@code listener} is already added.
*/
@AddedInOrBefore(majorVersion = 33)
public void addResourceOveruseListener(
@NonNull @CallbackExecutor Executor executor,
@ResourceOveruseFlag int resourceOveruseFlag,
@NonNull ResourceOveruseListener listener) {
Objects.requireNonNull(listener, "Listener must be non-null");
Objects.requireNonNull(executor, "Executor must be non-null");
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
boolean shouldRemoveFromService;
boolean shouldAddToService;
synchronized (mLock) {
ResourceOveruseListenerInfo listenerInfo =
new ResourceOveruseListenerInfo(listener, executor, resourceOveruseFlag);
if (mResourceOveruseListenerInfos.contains(listenerInfo)) {
throw new IllegalStateException(
"Cannot add the listener as it is already added");
}
shouldRemoveFromService = mResourceOveruseListenerImpl.hasListeners();
shouldAddToService = mResourceOveruseListenerImpl.maybeAppendFlag(resourceOveruseFlag);
mResourceOveruseListenerInfos.add(listenerInfo);
}
if (shouldAddToService) {
if (shouldRemoveFromService) {
removeResourceOveruseListenerImpl();
}
addResourceOveruseListenerImpl();
}
}
/**
* Removes the {@link ResourceOveruseListener} for the calling package.
*
* @param listener Listener implementing {@link ResourceOveruseListener} interface.
*/
@AddedInOrBefore(majorVersion = 33)
public void removeResourceOveruseListener(@NonNull ResourceOveruseListener listener) {
Objects.requireNonNull(listener, "Listener must be non-null");
boolean shouldRemoveFromService;
boolean shouldReAddToService;
synchronized (mLock) {
int index = 0;
int resourceOveruseFlag = 0;
for (; index != mResourceOveruseListenerInfos.size(); ++index) {
ResourceOveruseListenerInfo listenerInfo = mResourceOveruseListenerInfos.get(index);
if (listenerInfo.listener == listener) {
resourceOveruseFlag = listenerInfo.resourceOveruseFlag;
break;
}
}
if (index == mResourceOveruseListenerInfos.size()) {
Log.w(TAG, "Cannot remove the listener. It has not been added.");
return;
}
mResourceOveruseListenerInfos.remove(index);
shouldRemoveFromService =
mResourceOveruseListenerImpl.maybeRemoveFlag(resourceOveruseFlag);
shouldReAddToService = mResourceOveruseListenerImpl.hasListeners();
}
if (shouldRemoveFromService) {
removeResourceOveruseListenerImpl();
if (shouldReAddToService) {
addResourceOveruseListenerImpl();
}
}
}
/**
* Adds {@link ResourceOveruseListener} to get resource overuse notifications for all packages.
*
* <p>Listening system services will get notified on any package overusing one of the resources
* specified at {@code resourceOveruseFlag}.
*
* @param listener Listener implementing {@link ResourceOveruseListener} interface.
* @param resourceOveruseFlag Flag to indicate the types of resource overuses to listen.
*
* @throws IllegalStateException if (@code listener} is already added.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
@AddedInOrBefore(majorVersion = 33)
public void addResourceOveruseListenerForSystem(
@NonNull @CallbackExecutor Executor executor,
@ResourceOveruseFlag int resourceOveruseFlag,
@NonNull ResourceOveruseListener listener) {
Objects.requireNonNull(listener, "Listener must be non-null");
Objects.requireNonNull(executor, "Executor must be non-null");
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
boolean shouldRemoveFromService;
boolean shouldAddToService;
synchronized (mLock) {
ResourceOveruseListenerInfo listenerInfo =
new ResourceOveruseListenerInfo(listener, executor, resourceOveruseFlag);
if (mResourceOveruseListenerForSystemInfos.contains(listenerInfo)) {
throw new IllegalStateException(
"Cannot add the listener as it is already added");
}
shouldRemoveFromService = mResourceOveruseListenerForSystemImpl.hasListeners();
shouldAddToService =
mResourceOveruseListenerForSystemImpl.maybeAppendFlag(resourceOveruseFlag);
mResourceOveruseListenerForSystemInfos.add(listenerInfo);
}
if (shouldAddToService) {
if (shouldRemoveFromService) {
removeResourceOveruseListenerForSystemImpl();
}
addResourceOveruseListenerForSystemImpl();
}
}
/**
* Removes {@link ResourceOveruseListener} from receiving system resource overuse notifications.
*
* @param listener Listener implementing {@link ResourceOveruseListener} interface.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
@AddedInOrBefore(majorVersion = 33)
public void removeResourceOveruseListenerForSystem(
@NonNull ResourceOveruseListener listener) {
Objects.requireNonNull(listener, "Listener must be non-null");
boolean shouldRemoveFromService;
boolean shouldReAddToService;
synchronized (mLock) {
int index = 0;
int resourceOveruseFlag = 0;
for (; index != mResourceOveruseListenerForSystemInfos.size(); ++index) {
ResourceOveruseListenerInfo listenerInfo =
mResourceOveruseListenerForSystemInfos.get(index);
if (listenerInfo.listener == listener) {
resourceOveruseFlag = listenerInfo.resourceOveruseFlag;
break;
}
}
if (index == mResourceOveruseListenerForSystemInfos.size()) {
Log.w(TAG, "Cannot remove the listener. It has not been added.");
return;
}
mResourceOveruseListenerForSystemInfos.remove(index);
shouldRemoveFromService =
mResourceOveruseListenerForSystemImpl.maybeRemoveFlag(resourceOveruseFlag);
shouldReAddToService = mResourceOveruseListenerForSystemImpl.hasListeners();
}
if (shouldRemoveFromService) {
removeResourceOveruseListenerForSystemImpl();
if (shouldReAddToService) {
addResourceOveruseListenerForSystemImpl();
}
}
}
/**
* Sets whether or not a package is killable on resource overuse.
*
* <p>Updating killable setting for package, whose state cannot be changed, will result in
* exception. This API may be used by CarSettings application or UI notification.
*
* @param packageName Name of the package whose setting should to be updated.
* Note: All packages under shared UID share the killable state as well. Thus
* setting the killable state for one package will set the killable state for
* all other packages that share a UID.
* @param userHandle User whose setting should be updated.
* @param isKillable Whether or not the package for the specified user is killable on resource
* overuse.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG)
@AddedInOrBefore(majorVersion = 33)
public void setKillablePackageAsUser(@NonNull String packageName,
@NonNull UserHandle userHandle, boolean isKillable) {
try {
mService.setKillablePackageAsUser(packageName, userHandle, isKillable);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
/**
* Returns the list of package killable states on resource overuse for the user.
*
* <p>This API may be used by CarSettings application or UI notification.
*
* @param userHandle User whose killable states for all packages should be returned.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG)
@NonNull
@AddedInOrBefore(majorVersion = 33)
public List<PackageKillableState> getPackageKillableStatesAsUser(
@NonNull UserHandle userHandle) {
try {
return mService.getPackageKillableStatesAsUser(userHandle);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, new ArrayList<>());
}
}
/**
* Sets the resource overuse configurations for the components provided in the configurations.
*
* <p>Must provide only one configuration per component. System services should set the
* configurations only for system and third-party components. Vendor services should set the
* configuration only for the vendor component.
*
* @param configurations List of resource overuse configurations. One configuration per
* component.
* @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to
* set.
*
* @return - {@link #RETURN_CODE_SUCCESS} if the set request is successful.
* - {@link #RETURN_CODE_ERROR} if the set request cannot be completed and the client
* should retry later.
*
* @throws IllegalArgumentException if {@code configurations} are invalid.
*
* @hide
*/
@SystemApi
@RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG)
@ReturnCode
@AddedInOrBefore(majorVersion = 33)
public int setResourceOveruseConfigurations(
@NonNull List<ResourceOveruseConfiguration> configurations,
@ResourceOveruseFlag int resourceOveruseFlag) {
try {
return mService.setResourceOveruseConfigurations(configurations, resourceOveruseFlag);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
return RETURN_CODE_ERROR;
}
}
/**
* Returns the current resource overuse configurations for all components.
*
* <p>This call is blocking and may take few seconds to return if the service is temporarily
* unavailable.
*
* @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to
* return.
*
* @return If the server process is alive and connected, returns list of available resource
* overuse configurations for all components. If the server process is dead,
* returns {@code null} value.
*
* @throws IllegalStateException if the system is in an invalid state.
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG,
Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS})
@Nullable
@AddedInOrBefore(majorVersion = 33)
public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations(
@ResourceOveruseFlag int resourceOveruseFlag) {
try {
return mService.getResourceOveruseConfigurations(resourceOveruseFlag);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, null);
}
}
/** @hide */
@Override
@AddedInOrBefore(majorVersion = 33)
public void onCarDisconnected() {
// nothing to do
}
private void checkClientStatus(int sessionId, int timeout) {
CarWatchdogClientCallback client;
Executor executor;
mMainHandler.removeCallbacks(mMainThreadCheck);
synchronized (mLock) {
if (mRegisteredClient == null) {
Log.w(TAG, "Cannot check client status. The client has not been registered.");
return;
}
mSession.currentId = sessionId;
client = mRegisteredClient;
executor = mCallbackExecutor;
mRemainingConditions = NUMBER_OF_CONDITIONS_TO_BE_MET;
}
// For a car watchdog client to be active, 1) its main thread is active and 2) the client
// responds within timeout. When each condition is met, the remaining task counter is
// decreased. If the remaining task counter is zero, the client is considered active.
mMainHandler.post(mMainThreadCheck);
// Call the client callback to check if the client is active.
executor.execute(() -> {
boolean checkDone = client.onCheckHealthStatus(sessionId, timeout);
if (checkDone) {
boolean shouldReport;
synchronized (mLock) {
if (mSession.lastReportedId == sessionId) {
return;
}
mSession.lastReportedId = sessionId;
mRemainingConditions--;
shouldReport = checkConditionLocked();
}
if (shouldReport) {
reportToService(sessionId);
}
}
});
}
private void checkMainThread() {
int sessionId;
boolean shouldReport;
synchronized (mLock) {
mRemainingConditions--;
sessionId = mSession.currentId;
shouldReport = checkConditionLocked();
}
if (shouldReport) {
reportToService(sessionId);
}
}
@GuardedBy("mLock")
private boolean checkConditionLocked() {
if (mRemainingConditions < 0) {
Log.wtf(TAG, "Remaining condition is less than zero: should not happen");
}
return mRemainingConditions == 0;
}
private void reportToService(int sessionId) {
try {
mService.tellClientAlive(mClientImpl, sessionId);
if (DEBUG) {
Log.d(TAG, "Informed CarService that client is alive");
}
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
private void notifyProcessTermination() {
CarWatchdogClientCallback client;
Executor executor;
synchronized (mLock) {
if (mRegisteredClient == null) {
Log.w(TAG, "Cannot notify the client. The client has not been registered.");
return;
}
client = mRegisteredClient;
executor = mCallbackExecutor;
}
executor.execute(() -> client.onPrepareProcessTermination());
}
private void addResourceOveruseListenerImpl() {
try {
mService.addResourceOveruseListener(
mResourceOveruseListenerImpl.resourceOveruseFlag(),
mResourceOveruseListenerImpl);
if (DEBUG) {
Log.d(TAG, "Resource overuse listener implementation is successfully added to "
+ "service");
}
} catch (RemoteException e) {
synchronized (mLock) {
mResourceOveruseListenerInfos.clear();
}
handleRemoteExceptionFromCarService(e);
}
}
private void removeResourceOveruseListenerImpl() {
try {
mService.removeResourceOveruseListener(mResourceOveruseListenerImpl);
if (DEBUG) {
Log.d(TAG, "Resource overuse listener implementation is successfully removed "
+ "from service");
}
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
private void addResourceOveruseListenerForSystemImpl() {
try {
mService.addResourceOveruseListenerForSystem(
mResourceOveruseListenerForSystemImpl.resourceOveruseFlag(),
mResourceOveruseListenerForSystemImpl);
if (DEBUG) {
Log.d(TAG, "Resource overuse listener for system implementation is successfully "
+ "added to service");
}
} catch (RemoteException e) {
synchronized (mLock) {
mResourceOveruseListenerForSystemInfos.clear();
}
handleRemoteExceptionFromCarService(e);
}
}
private void removeResourceOveruseListenerForSystemImpl() {
try {
mService.removeResourceOveruseListenerForSystem(mResourceOveruseListenerForSystemImpl);
if (DEBUG) {
Log.d(TAG, "Resource overuse listener for system implementation is successfully "
+ "removed from service");
}
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
private void onResourceOveruse(ResourceOveruseStats resourceOveruseStats, boolean isSystem) {
if (resourceOveruseStats.getIoOveruseStats() == null) {
Log.w(TAG, "Skipping resource overuse notification as the stats are missing");
return;
}
List<ResourceOveruseListenerInfo> listenerInfos;
synchronized (mLock) {
if (isSystem) {
listenerInfos = mResourceOveruseListenerForSystemInfos;
} else {
listenerInfos = mResourceOveruseListenerInfos;
}
}
if (listenerInfos.isEmpty()) {
Log.w(TAG, "Cannot notify resource overuse listener " + (isSystem ? "for system " : "")
+ "as it is not registered.");
return;
}
for (ResourceOveruseListenerInfo listenerInfo : listenerInfos) {
if ((listenerInfo.resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) == 1) {
listenerInfo.executor.execute(() -> {
listenerInfo.listener.onOveruse(resourceOveruseStats);
});
}
}
}
/** @hide */
private static final class ICarWatchdogClientImpl extends ICarWatchdogServiceCallback.Stub {
private final WeakReference<CarWatchdogManager> mManager;
ICarWatchdogClientImpl(CarWatchdogManager manager) {
mManager = new WeakReference<>(manager);
}
@Override
public void onCheckHealthStatus(int sessionId, int timeout) {
CarWatchdogManager manager = mManager.get();
if (manager != null) {
manager.checkClientStatus(sessionId, timeout);
}
}
@Override
public void onPrepareProcessTermination() {
CarWatchdogManager manager = mManager.get();
if (manager != null) {
manager.notifyProcessTermination();
}
}
}
private static final class SessionInfo {
public int currentId;
public int lastReportedId;
SessionInfo(int currentId, int lastReportedId) {
this.currentId = currentId;
this.lastReportedId = lastReportedId;
}
}
/** @hide */
private static final class IResourceOveruseListenerImpl extends IResourceOveruseListener.Stub {
private static final int[] RESOURCE_OVERUSE_FLAGS = new int[]{ FLAG_RESOURCE_OVERUSE_IO };
private final WeakReference<CarWatchdogManager> mManager;
private final boolean mIsSystem;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SparseIntArray mNumListenersByResource;
IResourceOveruseListenerImpl(CarWatchdogManager manager, boolean isSystem) {
mManager = new WeakReference<>(manager);
mIsSystem = isSystem;
mNumListenersByResource = new SparseIntArray();
}
@Override
public void onOveruse(ResourceOveruseStats resourceOveruserStats) {
CarWatchdogManager manager = mManager.get();
if (manager != null) {
manager.onResourceOveruse(resourceOveruserStats, mIsSystem);
}
}
public boolean hasListeners() {
synchronized (mLock) {
return mNumListenersByResource.size() != 0;
}
}
public boolean maybeAppendFlag(int appendFlag) {
boolean isChanged = false;
synchronized (mLock) {
for (int flag : RESOURCE_OVERUSE_FLAGS) {
if ((appendFlag & flag) != 1) {
continue;
}
int value = mNumListenersByResource.get(flag, 0);
isChanged = ++value == 1;
mNumListenersByResource.put(flag, value);
}
}
return isChanged;
}
public boolean maybeRemoveFlag(int removeFlag) {
boolean isChanged = false;
synchronized (mLock) {
for (int flag : RESOURCE_OVERUSE_FLAGS) {
if ((removeFlag & flag) != 1) {
continue;
}
int value = mNumListenersByResource.get(flag, 0);
if (value == 0) {
continue;
}
if (--value == 0) {
isChanged = true;
mNumListenersByResource.delete(flag);
} else {
mNumListenersByResource.put(flag, value);
}
}
}
return isChanged;
}
public int resourceOveruseFlag() {
synchronized (mLock) {
int flag = 0;
for (int i = 0; i < mNumListenersByResource.size(); ++i) {
flag |= mNumListenersByResource.valueAt(i) > 0 ? mNumListenersByResource.keyAt(
i) : 0;
}
return flag;
}
}
}
/** @hide */
private static final class ResourceOveruseListenerInfo {
public final ResourceOveruseListener listener;
public final Executor executor;
public final int resourceOveruseFlag;
ResourceOveruseListenerInfo(ResourceOveruseListener listener,
Executor executor, int resourceOveruseFlag) {
this.listener = listener;
this.executor = executor;
this.resourceOveruseFlag = resourceOveruseFlag;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ResourceOveruseListenerInfo)) {
return false;
}
ResourceOveruseListenerInfo listenerInfo = (ResourceOveruseListenerInfo) obj;
// The ResourceOveruseListenerInfo equality is solely based on the listener because
// the clients shouldn't register the same listener multiple times. When checking
// whether a listener is previously registered, this equality check is used.
return listenerInfo.listener == listener;
}
@Override
public int hashCode() {
// Similar to equality check, the hash generator uses only the listener.
return Objects.hash(listener);
}
}
}