/* * 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.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; 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 static final int MAX_UNREGISTER_CLIENT_WAIT_MILLIS = 200; private final Runnable mMainThreadCheck = () -> checkMainThread(); /** * Timeout for services which should be responsive. The length is 3,000 milliseconds. * * @hide */ @SystemApi public static final int TIMEOUT_CRITICAL = 0; /** * Timeout for services which are relatively responsive. The length is 5,000 milliseconds. * * @hide */ @SystemApi public static final int TIMEOUT_MODERATE = 1; /** * Timeout for all other services. The length is 10,000 milliseconds. * * @hide */ @SystemApi 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 mResourceOveruseListenerInfos; @GuardedBy("mLock") private final List mResourceOveruseListenerForSystemInfos; @GuardedBy("mLock") private final ClientInfo mHealthCheckingClient = new ClientInfo(); @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. * *

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. * *

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. */ public boolean onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout) { return false; } /** * Car watchdog server notifies the client that it will be terminated in 1 second. * *

The callback method is called at the Executor which is specified in {@link * CarWatchdogManager#registerClient}. */ 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}. * *

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) 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 (CarWatchdogManager.this.mLock) { if (mHealthCheckingClient.hasClientLocked()) { if (mHealthCheckingClient.callback == client) { return; } throw new IllegalStateException( "Cannot register the client. Only one client can be registered."); } mHealthCheckingClient.setClientLocked(client, executor); } try { mService.registerClient(mClientImpl, timeout); if (DEBUG) { Slog.d(TAG, "Car watchdog client is successfully registered"); } } catch (RemoteException e) { synchronized (mLock) { mHealthCheckingClient.resetClientLocked(); } handleRemoteExceptionFromCarService(e); } finally { synchronized (mLock) { if (mHealthCheckingClient.hasClientLocked()) { mHealthCheckingClient.setRegistrationCompletedLocked(); } } } } /** * Unregisters the car watchdog client from {@link CarWatchdogManager}. * * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface. * * @hide */ @SystemApi @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG) public void unregisterClient(@NonNull CarWatchdogClientCallback client) { Objects.requireNonNull(client, "Client must be non-null"); synchronized (CarWatchdogManager.this.mLock) { if (!mHealthCheckingClient.hasClientLocked() || mHealthCheckingClient.callback != client) { Slog.w(TAG, "Cannot unregister the client. It has not been registered."); return; } if (mHealthCheckingClient.isRegistrationInProgressLocked()) { throwIllegalStateExceptionOnTargetSdkPostUdc( "Cannot unregister the client while a registration is in progress"); return; } mHealthCheckingClient.resetClientLocked(); } try { mService.unregisterClient(mClientImpl); if (DEBUG) { Slog.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) public void tellClientAlive(@NonNull CarWatchdogClientCallback client, int sessionId) { Objects.requireNonNull(client, "Client must be non-null"); boolean shouldReport; synchronized (CarWatchdogManager.this.mLock) { if (!mHealthCheckingClient.hasClientLocked() || mHealthCheckingClient.callback != 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) { Slog.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. */ public static final int STATS_PERIOD_CURRENT_DAY = 1; public static final int STATS_PERIOD_PAST_3_DAYS = 2; public static final int STATS_PERIOD_PAST_7_DAYS = 3; public static final int STATS_PERIOD_PAST_15_DAYS = 4; public static final int STATS_PERIOD_PAST_30_DAYS = 5; /** * Constants that define the type of resource overuse. */ 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 public static final int FLAG_MINIMUM_STATS_IO_1_MB = 1 << 0; /** @hide */ @SystemApi public static final int FLAG_MINIMUM_STATS_IO_100_MB = 1 << 1; /** @hide */ @SystemApi public static final int FLAG_MINIMUM_STATS_IO_1_GB = 1 << 2; // Return codes used to indicate the result of a request. /** @hide */ @SystemApi public static final int RETURN_CODE_SUCCESS = 0; /** @hide */ @SystemApi 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 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 public List 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 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. * *

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. * *

The listener is called at the executor which is specified in {@link * CarWatchdogManager#addResourceOveruseListener} or * {@code addResourceOveruseListenerForSystem}. * *

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. */ void onOveruse(@NonNull ResourceOveruseStats resourceOveruseStats); } /** * Adds the {@link ResourceOveruseListener} for the calling package. * *

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. */ 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. */ 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()) { Slog.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. * *

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) 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) 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()) { Slog.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. * *

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) 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. * *

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 public List 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. * *

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 public int setResourceOveruseConfigurations( @NonNull List 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. * *

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 public List getResourceOveruseConfigurations( @ResourceOveruseFlag int resourceOveruseFlag) { try { return mService.getResourceOveruseConfigurations(resourceOveruseFlag); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, null); } } /** @hide */ @Override public void onCarDisconnected() { // nothing to do } private void throwIllegalStateExceptionOnTargetSdkPostUdc(String message) { if (getContext().getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { throw new IllegalStateException(message); } Slog.e(TAG, "Suppressing illegal state exception on target SDK <= UDC: " + message); } private void checkClientStatus(int sessionId, int timeout) { CarWatchdogClientCallback clientCallback; Executor executor; mMainHandler.removeCallbacks(mMainThreadCheck); synchronized (CarWatchdogManager.this.mLock) { if (!mHealthCheckingClient.hasClientLocked()) { Slog.w(TAG, "Cannot check client status. The client has not been registered."); return; } mSession.currentId = sessionId; clientCallback = mHealthCheckingClient.callback; executor = mHealthCheckingClient.executor; 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 = clientCallback.onCheckHealthStatus(sessionId, timeout); Slog.i(TAG, "Called clientCallback.onCheckHealthStatus"); 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) { Slog.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) { Slog.d(TAG, "Informed CarService that client is alive"); } } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } } private void notifyProcessTermination() { CarWatchdogClientCallback clientCallback; Executor executor; synchronized (CarWatchdogManager.this.mLock) { if (!mHealthCheckingClient.hasClientLocked()) { Slog.w(TAG, "Cannot notify the client. The client has not been registered."); return; } clientCallback = mHealthCheckingClient.callback; executor = mHealthCheckingClient.executor; } executor.execute(() -> clientCallback.onPrepareProcessTermination()); } private void addResourceOveruseListenerImpl() { try { mService.addResourceOveruseListener( mResourceOveruseListenerImpl.resourceOveruseFlag(), mResourceOveruseListenerImpl); if (DEBUG) { Slog.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) { Slog.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) { Slog.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) { Slog.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) { Slog.w(TAG, "Skipping resource overuse notification as the stats are missing"); return; } List listenerInfos; synchronized (mLock) { if (isSystem) { listenerInfos = mResourceOveruseListenerForSystemInfos; } else { listenerInfos = mResourceOveruseListenerInfos; } } if (listenerInfos.isEmpty()) { Slog.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 final class ClientInfo { public CarWatchdogClientCallback callback; public Executor executor; private boolean mIsRegistrationInProgress; @GuardedBy("CarWatchdogManager.this.mLock") boolean hasClientLocked() { return callback != null && executor != null; } @GuardedBy("CarWatchdogManager.this.mLock") void setClientLocked(CarWatchdogClientCallback callback, Executor executor) { this.callback = callback; this.executor = executor; mIsRegistrationInProgress = true; if (DEBUG) { Slog.d(TAG, "Set CarWatchdog client callback to " + callback); } } @GuardedBy("CarWatchdogManager.this.mLock") void resetClientLocked() { callback = null; executor = null; mIsRegistrationInProgress = false; if (DEBUG) { Slog.d(TAG, "Reset CarWatchdog client callback"); } } @GuardedBy("CarWatchdogManager.this.mLock") void setRegistrationCompletedLocked() { mIsRegistrationInProgress = false; mLock.notify(); if (DEBUG) { Slog.d(TAG, "Marked registration completed"); } } @GuardedBy("CarWatchdogManager.this.mLock") boolean isRegistrationInProgressLocked() { long nowMillis = System.currentTimeMillis(); long endMillis = nowMillis + MAX_UNREGISTER_CLIENT_WAIT_MILLIS; while (mIsRegistrationInProgress && nowMillis < endMillis) { try { mLock.wait(endMillis - nowMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Slog.w(TAG, "Interrupted while waiting for registration to complete. " + "Continuing to wait"); } finally { nowMillis = System.currentTimeMillis(); } } return mIsRegistrationInProgress; } } /** @hide */ private static final class ICarWatchdogClientImpl extends ICarWatchdogServiceCallback.Stub { private final WeakReference 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 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); } } }