• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.car.watchdog;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.car.Car;
26 import android.car.CarManagerBase;
27 import android.car.annotation.AddedInOrBefore;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.util.Log;
34 import android.util.SparseIntArray;
35 
36 import com.android.internal.annotations.GuardedBy;
37 import com.android.internal.util.Preconditions;
38 
39 import java.lang.annotation.ElementType;
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.lang.annotation.Target;
43 import java.lang.ref.WeakReference;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.concurrent.Executor;
48 
49 /**
50  * CarWatchdogManager allows applications to collect latest system resource overuse statistics, add
51  * listener for resource overuse notifications, and update resource overuse configurations.
52  */
53 public final class CarWatchdogManager extends CarManagerBase {
54 
55     private static final String TAG = CarWatchdogManager.class.getSimpleName();
56     private static final boolean DEBUG = false; // STOPSHIP if true
57     private static final int INVALID_SESSION_ID = -1;
58     private static final int NUMBER_OF_CONDITIONS_TO_BE_MET = 2;
59 
60     private final Runnable mMainThreadCheck = () -> checkMainThread();
61 
62     /**
63      * Timeout for services which should be responsive. The length is 3,000 milliseconds.
64      *
65      * @hide
66      */
67     @SystemApi
68     @AddedInOrBefore(majorVersion = 33)
69     public static final int TIMEOUT_CRITICAL = 0;
70 
71     /**
72      * Timeout for services which are relatively responsive. The length is 5,000 milliseconds.
73      *
74      * @hide
75      */
76     @SystemApi
77     @AddedInOrBefore(majorVersion = 33)
78     public static final int TIMEOUT_MODERATE = 1;
79 
80     /**
81      * Timeout for all other services. The length is 10,000 milliseconds.
82      *
83      * @hide
84      */
85     @SystemApi
86     @AddedInOrBefore(majorVersion = 33)
87     public static final int TIMEOUT_NORMAL = 2;
88 
89     /** @hide */
90     @Retention(RetentionPolicy.SOURCE)
91     @IntDef(prefix = "TIMEOUT_", value = {
92             TIMEOUT_CRITICAL,
93             TIMEOUT_MODERATE,
94             TIMEOUT_NORMAL,
95     })
96     @Target({ElementType.TYPE_USE})
97     public @interface TimeoutLengthEnum {}
98 
99     private final ICarWatchdogService mService;
100     private final ICarWatchdogClientImpl mClientImpl;
101     private final IResourceOveruseListenerImpl mResourceOveruseListenerImpl;
102     private final IResourceOveruseListenerImpl mResourceOveruseListenerForSystemImpl;
103     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
104 
105     private final Object mLock = new Object();
106     @GuardedBy("mLock")
107     private final SessionInfo mSession = new SessionInfo(INVALID_SESSION_ID, INVALID_SESSION_ID);
108     @GuardedBy("mLock")
109     private final List<ResourceOveruseListenerInfo> mResourceOveruseListenerInfos;
110     @GuardedBy("mLock")
111     private final List<ResourceOveruseListenerInfo> mResourceOveruseListenerForSystemInfos;
112     @GuardedBy("mLock")
113     private CarWatchdogClientCallback mRegisteredClient;
114     @GuardedBy("mLock")
115     private Executor mCallbackExecutor;
116     @GuardedBy("mLock")
117     private int mRemainingConditions;
118 
119     /**
120      * CarWatchdogClientCallback is implemented by the clients which want to be health-checked by
121      * car watchdog server. Every time onCheckHealthStatus is called, they are expected to
122      * respond by calling {@link #tellClientAlive} within timeout. If they don't
123      * respond, car watchdog server reports the current state and kills them.
124      *
125      * <p>Before car watchdog server kills the client, it calls onPrepareProcessTermination to allow
126      * them to prepare the termination. They will be killed in 1 second.
127      *
128      * @hide
129      */
130     @SystemApi
131     public abstract static class CarWatchdogClientCallback {
132         /**
133          * Car watchdog server pings the client to check if it is alive.
134          *
135          * <p>The callback method is called at the Executor which is specified in {@link
136          * CarWatchdogManager#registerClient}.
137          *
138          * @param sessionId Unique id to distinguish each health checking.
139          * @param timeout Time duration within which the client should respond.
140          *
141          * @return whether the response is immediately acknowledged. If {@code true}, car watchdog
142          *         server considers that the response is acknowledged already. If {@code false},
143          *         the client should call {@link CarWatchdogManager#tellClientAlive} later to tell
144          *         that it is alive.
145          */
146         @AddedInOrBefore(majorVersion = 33)
onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout)147         public boolean onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout) {
148             return false;
149         }
150 
151         /**
152          * Car watchdog server notifies the client that it will be terminated in 1 second.
153          *
154          * <p>The callback method is called at the Executor which is specified in {@link
155          * CarWatchdogManager#registerClient}.
156          */
157         @AddedInOrBefore(majorVersion = 33)
onPrepareProcessTermination()158         public void onPrepareProcessTermination() {}
159     }
160 
161     /** @hide */
CarWatchdogManager(Car car, IBinder service)162     public CarWatchdogManager(Car car, IBinder service) {
163         super(car);
164         mService = ICarWatchdogService.Stub.asInterface(service);
165         mClientImpl = new ICarWatchdogClientImpl(this);
166         mResourceOveruseListenerImpl = new IResourceOveruseListenerImpl(this, /* isSystem= */false);
167         mResourceOveruseListenerForSystemImpl = new IResourceOveruseListenerImpl(this,
168                 /* isSystem= */true);
169         mResourceOveruseListenerInfos = new ArrayList<>();
170         mResourceOveruseListenerForSystemInfos = new ArrayList<>();
171     }
172 
173     /**
174      * Registers the car watchdog clients to {@link CarWatchdogManager}.
175      *
176      * <p>It is allowed to register a client from any thread, but only one client can be
177      * registered. If two or more clients are needed, create a new {@link Car} and register a client
178      * to it.
179      *
180      * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
181      * @param timeout The time duration within which the client desires to respond. The actual
182      *        timeout is decided by watchdog server.
183      * @throws IllegalStateException if at least one client is already registered.
184      *
185      * @hide
186      */
187     @SystemApi
188     @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
189     @AddedInOrBefore(majorVersion = 33)
registerClient(@onNull @allbackExecutor Executor executor, @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout)190     public void registerClient(@NonNull @CallbackExecutor Executor executor,
191             @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout) {
192         Objects.requireNonNull(client, "Client must be non-null");
193         Objects.requireNonNull(executor, "Executor must be non-null");
194         synchronized (mLock) {
195             if (mRegisteredClient == client) {
196                 return;
197             }
198             if (mRegisteredClient != null) {
199                 throw new IllegalStateException(
200                         "Cannot register the client. Only one client can be registered.");
201             }
202             mRegisteredClient = client;
203             mCallbackExecutor = executor;
204         }
205         try {
206             mService.registerClient(mClientImpl, timeout);
207             if (DEBUG) {
208                 Log.d(TAG, "Car watchdog client is successfully registered");
209             }
210         } catch (RemoteException e) {
211             synchronized (mLock) {
212                 mRegisteredClient = null;
213             }
214             handleRemoteExceptionFromCarService(e);
215         }
216     }
217 
218     /**
219      * Unregisters the car watchdog client from {@link CarWatchdogManager}.
220      *
221      * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
222      *
223      * @hide
224      */
225     @SystemApi
226     @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
227     @AddedInOrBefore(majorVersion = 33)
unregisterClient(@onNull CarWatchdogClientCallback client)228     public void unregisterClient(@NonNull CarWatchdogClientCallback client) {
229         Objects.requireNonNull(client, "Client must be non-null");
230         synchronized (mLock) {
231             if (mRegisteredClient != client) {
232                 Log.w(TAG, "Cannot unregister the client. It has not been registered.");
233                 return;
234             }
235             mRegisteredClient = null;
236             mCallbackExecutor = null;
237         }
238         try {
239             mService.unregisterClient(mClientImpl);
240             if (DEBUG) {
241                 Log.d(TAG, "Car watchdog client is successfully unregistered");
242             }
243         } catch (RemoteException e) {
244             handleRemoteExceptionFromCarService(e);
245         }
246     }
247 
248     /**
249      * Tells {@link CarWatchdogManager} that the client is alive.
250      *
251      * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
252      * @param sessionId Session id given by {@link CarWatchdogManager}.
253      * @throws IllegalStateException if {@code client} is not registered.
254      * @throws IllegalArgumentException if {@code sessionId} is not correct.
255      *
256      * @hide
257      */
258     @SystemApi
259     @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
260     @AddedInOrBefore(majorVersion = 33)
tellClientAlive(@onNull CarWatchdogClientCallback client, int sessionId)261     public void tellClientAlive(@NonNull CarWatchdogClientCallback client, int sessionId) {
262         Objects.requireNonNull(client, "Client must be non-null");
263         boolean shouldReport;
264         synchronized (mLock) {
265             if (mRegisteredClient != client) {
266                 throw new IllegalStateException(
267                         "Cannot report client status. The client has not been registered.");
268             }
269             Preconditions.checkArgument(sessionId != -1 && mSession.currentId == sessionId,
270                     "Cannot report client status. Received session id (" + sessionId
271                             + ") doesn't match the current one (" + mSession.currentId + ").");
272             if (mSession.lastReportedId == sessionId) {
273                 Log.w(TAG, "The given session id is already reported.");
274                 return;
275             }
276             mSession.lastReportedId = sessionId;
277             mRemainingConditions--;
278             shouldReport = checkConditionLocked();
279         }
280         if (shouldReport) {
281             reportToService(sessionId);
282         }
283     }
284 
285     /** @hide */
286     @IntDef(flag = false, prefix = { "STATS_PERIOD_" }, value = {
287         STATS_PERIOD_CURRENT_DAY,
288         STATS_PERIOD_PAST_3_DAYS,
289         STATS_PERIOD_PAST_7_DAYS,
290         STATS_PERIOD_PAST_15_DAYS,
291         STATS_PERIOD_PAST_30_DAYS,
292     })
293     @Retention(RetentionPolicy.SOURCE)
294     public @interface StatsPeriod {}
295 
296     /** @hide */
297     @IntDef(flag = true, prefix = { "FLAG_RESOURCE_OVERUSE_" }, value = {
298             FLAG_RESOURCE_OVERUSE_IO,
299     })
300     @Retention(RetentionPolicy.SOURCE)
301     public @interface ResourceOveruseFlag {}
302 
303     /** @hide */
304     @IntDef(flag = true, prefix = { "FLAG_MINIMUM_STATS_" }, value = {
305             FLAG_MINIMUM_STATS_IO_1_MB,
306             FLAG_MINIMUM_STATS_IO_100_MB,
307             FLAG_MINIMUM_STATS_IO_1_GB,
308     })
309     @Retention(RetentionPolicy.SOURCE)
310     public @interface MinimumStatsFlag {}
311 
312     /** @hide */
313     @IntDef(flag = true, prefix = { "RETURN_CODE_" }, value = {
314             RETURN_CODE_SUCCESS,
315             RETURN_CODE_ERROR,
316     })
317     @Retention(RetentionPolicy.SOURCE)
318     public @interface ReturnCode {}
319 
320     /**
321      * Constants that define the stats period in days.
322      */
323     @AddedInOrBefore(majorVersion = 33)
324     public static final int STATS_PERIOD_CURRENT_DAY = 1;
325     @AddedInOrBefore(majorVersion = 33)
326     public static final int STATS_PERIOD_PAST_3_DAYS = 2;
327     @AddedInOrBefore(majorVersion = 33)
328     public static final int STATS_PERIOD_PAST_7_DAYS = 3;
329     @AddedInOrBefore(majorVersion = 33)
330     public static final int STATS_PERIOD_PAST_15_DAYS = 4;
331     @AddedInOrBefore(majorVersion = 33)
332     public static final int STATS_PERIOD_PAST_30_DAYS = 5;
333 
334     /**
335      * Constants that define the type of resource overuse.
336      */
337     @AddedInOrBefore(majorVersion = 33)
338     public static final int FLAG_RESOURCE_OVERUSE_IO = 1 << 0;
339 
340     /**
341      * Constants that define the minimum stats for each resource type.
342      *
343      * Below constants specify the minimum amount of data written to disk.
344      *
345      * @hide
346      */
347     @SystemApi
348     @AddedInOrBefore(majorVersion = 33)
349     public static final int FLAG_MINIMUM_STATS_IO_1_MB = 1 << 0;
350     /** @hide */
351     @SystemApi
352     @AddedInOrBefore(majorVersion = 33)
353     public static final int FLAG_MINIMUM_STATS_IO_100_MB = 1 << 1;
354     /** @hide */
355     @SystemApi
356     @AddedInOrBefore(majorVersion = 33)
357     public static final int FLAG_MINIMUM_STATS_IO_1_GB = 1 << 2;
358 
359     // Return codes used to indicate the result of a request.
360     /** @hide */
361     @SystemApi
362     @AddedInOrBefore(majorVersion = 33)
363     public static final int RETURN_CODE_SUCCESS = 0;
364     /** @hide */
365     @SystemApi
366     @AddedInOrBefore(majorVersion = 33)
367     public static final int RETURN_CODE_ERROR = -1;
368 
369     /**
370      * Returns resource overuse stats for the calling package. Returns {@code null}, if no stats.
371      *
372      * @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
373      * @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
374      *
375      * @return Resource overuse stats for the calling package. If the calling package has no stats
376      *         for a specified resource overuse type, null value is returned for the corresponding
377      *         resource overuse stats. If the calling package doesn't have sufficient stats for
378      *         {@code maxStatsPeriod} for a specified resource overuse type, the stats are returned
379      *         only for the period returned in the individual resource overuse stats.
380      */
381     @NonNull
382     @AddedInOrBefore(majorVersion = 33)
getResourceOveruseStats( @esourceOveruseFlag int resourceOveruseFlag, @StatsPeriod int maxStatsPeriod)383     public ResourceOveruseStats getResourceOveruseStats(
384             @ResourceOveruseFlag int resourceOveruseFlag,
385             @StatsPeriod int maxStatsPeriod) {
386         try {
387             return mService.getResourceOveruseStats(resourceOveruseFlag, maxStatsPeriod);
388         } catch (RemoteException e) {
389             ResourceOveruseStats.Builder builder =
390                     new ResourceOveruseStats.Builder("", UserHandle.CURRENT);
391             return handleRemoteExceptionFromCarService(e, builder.build());
392         }
393     }
394 
395     /**
396      * Returns resource overuse stats for all monitored packages.
397      *
398      * @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
399      * @param minimumStatsFlag Flag to specify the minimum stats for each resource overuse type.
400      *                         Only stats above the specified minimum stats for a resource overuse
401      *                         type will be returned. May provide only one minimum stats flag for
402      *                         each resource overuse type. When no minimum stats flag is specified,
403      *                         all stats are returned.
404      * @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
405      *
406      * @return Resource overuse stats for all monitored packages. If any package doesn't have stats
407      *         for a specified resource type, null value is returned for the corresponding resource
408      *         overuse stats. If any package doesn't have sufficient stats for
409      *         {@code maxStatsPeriod} for a specified resource overuse type, the stats are returned
410      *         only for the period returned in the individual resource stats.
411      *
412      * @hide
413      */
414     @SystemApi
415     @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
416     @NonNull
417     @AddedInOrBefore(majorVersion = 33)
getAllResourceOveruseStats( @esourceOveruseFlag int resourceOveruseFlag, @MinimumStatsFlag int minimumStatsFlag, @StatsPeriod int maxStatsPeriod)418     public List<ResourceOveruseStats> getAllResourceOveruseStats(
419             @ResourceOveruseFlag int resourceOveruseFlag,
420             @MinimumStatsFlag int minimumStatsFlag,
421             @StatsPeriod int maxStatsPeriod) {
422         try {
423             return mService.getAllResourceOveruseStats(resourceOveruseFlag, minimumStatsFlag,
424                     maxStatsPeriod);
425         } catch (RemoteException e) {
426             return handleRemoteExceptionFromCarService(e, new ArrayList<>());
427         }
428     }
429 
430     /**
431      * Returns resource overuse stats for a specific user package.
432      *
433      * @param packageName Name of the package whose stats should be returned.
434      * @param userHandle Handle of the user whose stats should be returned.
435      * @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
436      * @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
437      *
438      * @return Resource overuse stats for the specified user package. If the user package has no
439      *         stats for a specified resource overuse type, null value is returned for the
440      *         corresponding resource overuse stats. If the user package doesn't have sufficient
441      *         stats for {@code maxStatsPeriod} for a specified resource overuse type, the stats are
442      *         returned only for the period returned in the individual resource overuse stats.
443      *
444      * @hide
445      */
446     @SystemApi
447     @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
448     @NonNull
449     @AddedInOrBefore(majorVersion = 33)
getResourceOveruseStatsForUserPackage( @onNull String packageName, @NonNull UserHandle userHandle, @ResourceOveruseFlag int resourceOveruseFlag, @StatsPeriod int maxStatsPeriod)450     public ResourceOveruseStats getResourceOveruseStatsForUserPackage(
451             @NonNull String packageName, @NonNull UserHandle userHandle,
452             @ResourceOveruseFlag int resourceOveruseFlag,
453             @StatsPeriod int maxStatsPeriod) {
454         try {
455             return mService.getResourceOveruseStatsForUserPackage(packageName, userHandle,
456                     resourceOveruseFlag, maxStatsPeriod);
457         } catch (RemoteException e) {
458             ResourceOveruseStats.Builder builder =
459                     new ResourceOveruseStats.Builder("", userHandle);
460             return handleRemoteExceptionFromCarService(e, builder.build());
461         }
462     }
463 
464     /**
465      * Listener to get resource overuse notifications.
466      *
467      * <p>Applications implement the listener method to take action and/or log on resource overuse.
468      */
469     public interface ResourceOveruseListener {
470         /**
471          * Called when a package either overuses a resource or about to overuse a resource.
472          *
473          * <p>The listener is called at the executor which is specified in {@link
474          * CarWatchdogManager#addResourceOveruseListener} or
475          * {@link CarWatchdogManager#addResourceOveruseListenerForSystem}.
476          *
477          * <p>The listener is called only on overusing one of the resources specified at the
478          * {@code resourceOveruseFlag} in {@link CarWatchdogManager#addResourceOveruseListener} or
479          * {@link CarWatchdogManager#addResourceOveruseListenerForSystem}.
480          *
481          * @param resourceOveruseStats Resource overuse stats containing stats only for resources
482          *                             overuse types that are either overused or about to be
483          *                             overused by the package. Implementations must check for null
484          *                             value in each resource overuse stats before reading the
485          *                             stats.
486          */
487         @AddedInOrBefore(majorVersion = 33)
onOveruse(@onNull ResourceOveruseStats resourceOveruseStats)488         void onOveruse(@NonNull ResourceOveruseStats resourceOveruseStats);
489     }
490 
491     /**
492      * Adds the {@link ResourceOveruseListener} for the calling package.
493      *
494      * <p>Resource overuse notifications are sent only for the calling package's resource overuse.
495      *
496      * @param listener Listener implementing {@link ResourceOveruseListener} interface.
497      * @param resourceOveruseFlag Flag to indicate the types of resource overuses to listen.
498      *
499      * @throws IllegalStateException if (@code listener} is already added.
500      */
501     @AddedInOrBefore(majorVersion = 33)
addResourceOveruseListener( @onNull @allbackExecutor Executor executor, @ResourceOveruseFlag int resourceOveruseFlag, @NonNull ResourceOveruseListener listener)502     public void addResourceOveruseListener(
503             @NonNull @CallbackExecutor Executor executor,
504             @ResourceOveruseFlag int resourceOveruseFlag,
505             @NonNull ResourceOveruseListener listener) {
506         Objects.requireNonNull(listener, "Listener must be non-null");
507         Objects.requireNonNull(executor, "Executor must be non-null");
508         Preconditions.checkArgument((resourceOveruseFlag > 0),
509                 "Must provide valid resource overuse flag");
510         boolean shouldRemoveFromService;
511         boolean shouldAddToService;
512         synchronized (mLock) {
513             ResourceOveruseListenerInfo listenerInfo =
514                     new ResourceOveruseListenerInfo(listener, executor, resourceOveruseFlag);
515             if (mResourceOveruseListenerInfos.contains(listenerInfo)) {
516                 throw new IllegalStateException(
517                         "Cannot add the listener as it is already added");
518             }
519             shouldRemoveFromService = mResourceOveruseListenerImpl.hasListeners();
520             shouldAddToService =  mResourceOveruseListenerImpl.maybeAppendFlag(resourceOveruseFlag);
521             mResourceOveruseListenerInfos.add(listenerInfo);
522         }
523         if (shouldAddToService) {
524             if (shouldRemoveFromService) {
525                 removeResourceOveruseListenerImpl();
526             }
527             addResourceOveruseListenerImpl();
528         }
529     }
530 
531     /**
532      * Removes the {@link ResourceOveruseListener} for the calling package.
533      *
534      * @param listener Listener implementing {@link ResourceOveruseListener} interface.
535      */
536     @AddedInOrBefore(majorVersion = 33)
removeResourceOveruseListener(@onNull ResourceOveruseListener listener)537     public void removeResourceOveruseListener(@NonNull ResourceOveruseListener listener) {
538         Objects.requireNonNull(listener, "Listener must be non-null");
539         boolean shouldRemoveFromService;
540         boolean shouldReAddToService;
541         synchronized (mLock) {
542             int index = 0;
543             int resourceOveruseFlag = 0;
544             for (; index != mResourceOveruseListenerInfos.size(); ++index) {
545                 ResourceOveruseListenerInfo listenerInfo = mResourceOveruseListenerInfos.get(index);
546                 if (listenerInfo.listener == listener) {
547                     resourceOveruseFlag = listenerInfo.resourceOveruseFlag;
548                     break;
549                 }
550             }
551             if (index == mResourceOveruseListenerInfos.size()) {
552                 Log.w(TAG, "Cannot remove the listener. It has not been added.");
553                 return;
554             }
555             mResourceOveruseListenerInfos.remove(index);
556             shouldRemoveFromService =
557                     mResourceOveruseListenerImpl.maybeRemoveFlag(resourceOveruseFlag);
558             shouldReAddToService = mResourceOveruseListenerImpl.hasListeners();
559         }
560         if (shouldRemoveFromService) {
561             removeResourceOveruseListenerImpl();
562             if (shouldReAddToService) {
563                 addResourceOveruseListenerImpl();
564             }
565         }
566     }
567 
568     /**
569      * Adds {@link ResourceOveruseListener} to get resource overuse notifications for all packages.
570      *
571      * <p>Listening system services will get notified on any package overusing one of the resources
572      * specified at {@code resourceOveruseFlag}.
573      *
574      * @param listener Listener implementing {@link ResourceOveruseListener} interface.
575      * @param resourceOveruseFlag Flag to indicate the types of resource overuses to listen.
576      *
577      * @throws IllegalStateException if (@code listener} is already added.
578      *
579      * @hide
580      */
581     @SystemApi
582     @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
583     @AddedInOrBefore(majorVersion = 33)
addResourceOveruseListenerForSystem( @onNull @allbackExecutor Executor executor, @ResourceOveruseFlag int resourceOveruseFlag, @NonNull ResourceOveruseListener listener)584     public void addResourceOveruseListenerForSystem(
585             @NonNull @CallbackExecutor Executor executor,
586             @ResourceOveruseFlag int resourceOveruseFlag,
587             @NonNull ResourceOveruseListener listener) {
588         Objects.requireNonNull(listener, "Listener must be non-null");
589         Objects.requireNonNull(executor, "Executor must be non-null");
590         Preconditions.checkArgument((resourceOveruseFlag > 0),
591                 "Must provide valid resource overuse flag");
592         boolean shouldRemoveFromService;
593         boolean shouldAddToService;
594         synchronized (mLock) {
595             ResourceOveruseListenerInfo listenerInfo =
596                     new ResourceOveruseListenerInfo(listener, executor, resourceOveruseFlag);
597             if (mResourceOveruseListenerForSystemInfos.contains(listenerInfo)) {
598                 throw new IllegalStateException(
599                         "Cannot add the listener as it is already added");
600             }
601             shouldRemoveFromService = mResourceOveruseListenerForSystemImpl.hasListeners();
602             shouldAddToService =
603                     mResourceOveruseListenerForSystemImpl.maybeAppendFlag(resourceOveruseFlag);
604             mResourceOveruseListenerForSystemInfos.add(listenerInfo);
605         }
606         if (shouldAddToService) {
607             if (shouldRemoveFromService) {
608                 removeResourceOveruseListenerForSystemImpl();
609             }
610             addResourceOveruseListenerForSystemImpl();
611         }
612     }
613 
614     /**
615      * Removes {@link ResourceOveruseListener} from receiving system resource overuse notifications.
616      *
617      * @param listener Listener implementing {@link ResourceOveruseListener} interface.
618      *
619      * @hide
620      */
621     @SystemApi
622     @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS)
623     @AddedInOrBefore(majorVersion = 33)
removeResourceOveruseListenerForSystem( @onNull ResourceOveruseListener listener)624     public void removeResourceOveruseListenerForSystem(
625             @NonNull ResourceOveruseListener listener) {
626         Objects.requireNonNull(listener, "Listener must be non-null");
627         boolean shouldRemoveFromService;
628         boolean shouldReAddToService;
629         synchronized (mLock) {
630             int index = 0;
631             int resourceOveruseFlag = 0;
632             for (; index != mResourceOveruseListenerForSystemInfos.size(); ++index) {
633                 ResourceOveruseListenerInfo listenerInfo =
634                         mResourceOveruseListenerForSystemInfos.get(index);
635                 if (listenerInfo.listener == listener) {
636                     resourceOveruseFlag = listenerInfo.resourceOveruseFlag;
637                     break;
638                 }
639             }
640             if (index == mResourceOveruseListenerForSystemInfos.size()) {
641                 Log.w(TAG, "Cannot remove the listener. It has not been added.");
642                 return;
643             }
644             mResourceOveruseListenerForSystemInfos.remove(index);
645             shouldRemoveFromService =
646                     mResourceOveruseListenerForSystemImpl.maybeRemoveFlag(resourceOveruseFlag);
647             shouldReAddToService = mResourceOveruseListenerForSystemImpl.hasListeners();
648         }
649         if (shouldRemoveFromService) {
650             removeResourceOveruseListenerForSystemImpl();
651             if (shouldReAddToService) {
652                 addResourceOveruseListenerForSystemImpl();
653             }
654         }
655     }
656 
657     /**
658      * Sets whether or not a package is killable on resource overuse.
659      *
660      * <p>Updating killable setting for package, whose state cannot be changed, will result in
661      * exception. This API may be used by CarSettings application or UI notification.
662      *
663      * @param packageName Name of the package whose setting should to be updated.
664      *                    Note: All packages under shared UID share the killable state as well. Thus
665      *                    setting the killable state for one package will set the killable state for
666      *                    all other packages that share a UID.
667      * @param userHandle  User whose setting should be updated.
668      * @param isKillable  Whether or not the package for the specified user is killable on resource
669      *                    overuse.
670      *
671      * @hide
672      */
673     @SystemApi
674     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG)
675     @AddedInOrBefore(majorVersion = 33)
setKillablePackageAsUser(@onNull String packageName, @NonNull UserHandle userHandle, boolean isKillable)676     public void setKillablePackageAsUser(@NonNull String packageName,
677             @NonNull UserHandle userHandle, boolean isKillable) {
678         try {
679             mService.setKillablePackageAsUser(packageName, userHandle, isKillable);
680         } catch (RemoteException e) {
681             handleRemoteExceptionFromCarService(e);
682         }
683     }
684 
685     /**
686      * Returns the list of package killable states on resource overuse for the user.
687      *
688      * <p>This API may be used by CarSettings application or UI notification.
689      *
690      * @param userHandle User whose killable states for all packages should be returned.
691      *
692      * @hide
693      */
694     @SystemApi
695     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG)
696     @NonNull
697     @AddedInOrBefore(majorVersion = 33)
getPackageKillableStatesAsUser( @onNull UserHandle userHandle)698     public List<PackageKillableState> getPackageKillableStatesAsUser(
699             @NonNull UserHandle userHandle) {
700         try {
701             return mService.getPackageKillableStatesAsUser(userHandle);
702         } catch (RemoteException e) {
703             return handleRemoteExceptionFromCarService(e, new ArrayList<>());
704         }
705     }
706 
707     /**
708      * Sets the resource overuse configurations for the components provided in the configurations.
709      *
710      * <p>Must provide only one configuration per component. System services should set the
711      * configurations only for system and third-party components. Vendor services should set the
712      * configuration only for the vendor component.
713      *
714      * @param configurations List of resource overuse configurations. One configuration per
715      *                       component.
716      * @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to
717      *                            set.
718      *
719      * @return - {@link #RETURN_CODE_SUCCESS} if the set request is successful.
720      *         - {@link #RETURN_CODE_ERROR} if the set request cannot be completed and the client
721      *         should retry later.
722      *
723      * @throws IllegalArgumentException if {@code configurations} are invalid.
724      *
725      * @hide
726      */
727     @SystemApi
728     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG)
729     @ReturnCode
730     @AddedInOrBefore(majorVersion = 33)
setResourceOveruseConfigurations( @onNull List<ResourceOveruseConfiguration> configurations, @ResourceOveruseFlag int resourceOveruseFlag)731     public int setResourceOveruseConfigurations(
732             @NonNull List<ResourceOveruseConfiguration> configurations,
733             @ResourceOveruseFlag int resourceOveruseFlag) {
734         try {
735             return mService.setResourceOveruseConfigurations(configurations, resourceOveruseFlag);
736         } catch (RemoteException e) {
737             handleRemoteExceptionFromCarService(e);
738             return RETURN_CODE_ERROR;
739         }
740     }
741 
742     /**
743      * Returns the current resource overuse configurations for all components.
744      *
745      * <p>This call is blocking and may take few seconds to return if the service is temporarily
746      * unavailable.
747      *
748      * @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to
749      *                            return.
750      *
751      * @return If the server process is alive and connected, returns list of available resource
752      *         overuse configurations for all components. If the server process is dead,
753      *         returns {@code null} value.
754      *
755      * @throws IllegalStateException if the system is in an invalid state.
756      * @hide
757      */
758     @SystemApi
759     @RequiresPermission(anyOf = {Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG,
760             Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS})
761     @Nullable
762     @AddedInOrBefore(majorVersion = 33)
getResourceOveruseConfigurations( @esourceOveruseFlag int resourceOveruseFlag)763     public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations(
764             @ResourceOveruseFlag int resourceOveruseFlag) {
765         try {
766             return mService.getResourceOveruseConfigurations(resourceOveruseFlag);
767         } catch (RemoteException e) {
768             return handleRemoteExceptionFromCarService(e, null);
769         }
770     }
771 
772     /** @hide */
773     @Override
774     @AddedInOrBefore(majorVersion = 33)
onCarDisconnected()775     public void onCarDisconnected() {
776         // nothing to do
777     }
778 
checkClientStatus(int sessionId, int timeout)779     private void checkClientStatus(int sessionId, int timeout) {
780         CarWatchdogClientCallback client;
781         Executor executor;
782         mMainHandler.removeCallbacks(mMainThreadCheck);
783         synchronized (mLock) {
784             if (mRegisteredClient == null) {
785                 Log.w(TAG, "Cannot check client status. The client has not been registered.");
786                 return;
787             }
788             mSession.currentId = sessionId;
789             client = mRegisteredClient;
790             executor = mCallbackExecutor;
791             mRemainingConditions = NUMBER_OF_CONDITIONS_TO_BE_MET;
792         }
793         // For a car watchdog client to be active, 1) its main thread is active and 2) the client
794         // responds within timeout. When each condition is met, the remaining task counter is
795         // decreased. If the remaining task counter is zero, the client is considered active.
796         mMainHandler.post(mMainThreadCheck);
797         // Call the client callback to check if the client is active.
798         executor.execute(() -> {
799             boolean checkDone = client.onCheckHealthStatus(sessionId, timeout);
800             if (checkDone) {
801                 boolean shouldReport;
802                 synchronized (mLock) {
803                     if (mSession.lastReportedId == sessionId) {
804                         return;
805                     }
806                     mSession.lastReportedId = sessionId;
807                     mRemainingConditions--;
808                     shouldReport = checkConditionLocked();
809                 }
810                 if (shouldReport) {
811                     reportToService(sessionId);
812                 }
813             }
814         });
815     }
816 
checkMainThread()817     private void checkMainThread() {
818         int sessionId;
819         boolean shouldReport;
820         synchronized (mLock) {
821             mRemainingConditions--;
822             sessionId = mSession.currentId;
823             shouldReport = checkConditionLocked();
824         }
825         if (shouldReport) {
826             reportToService(sessionId);
827         }
828     }
829 
checkConditionLocked()830     private boolean checkConditionLocked() {
831         if (mRemainingConditions < 0) {
832             Log.wtf(TAG, "Remaining condition is less than zero: should not happen");
833         }
834         return mRemainingConditions == 0;
835     }
836 
reportToService(int sessionId)837     private void reportToService(int sessionId) {
838         try {
839             mService.tellClientAlive(mClientImpl, sessionId);
840             if (DEBUG) {
841                 Log.d(TAG, "Informed CarService that client is alive");
842             }
843         } catch (RemoteException e) {
844             handleRemoteExceptionFromCarService(e);
845         }
846     }
847 
notifyProcessTermination()848     private void notifyProcessTermination() {
849         CarWatchdogClientCallback client;
850         Executor executor;
851         synchronized (mLock) {
852             if (mRegisteredClient == null) {
853                 Log.w(TAG, "Cannot notify the client. The client has not been registered.");
854                 return;
855             }
856             client = mRegisteredClient;
857             executor = mCallbackExecutor;
858         }
859         executor.execute(() -> client.onPrepareProcessTermination());
860     }
861 
addResourceOveruseListenerImpl()862     private void addResourceOveruseListenerImpl() {
863         try {
864             mService.addResourceOveruseListener(
865                     mResourceOveruseListenerImpl.resourceOveruseFlag(),
866                     mResourceOveruseListenerImpl);
867             if (DEBUG) {
868                 Log.d(TAG, "Resource overuse listener implementation is successfully added to "
869                         + "service");
870             }
871         } catch (RemoteException e) {
872             synchronized (mLock) {
873                 mResourceOveruseListenerInfos.clear();
874             }
875             handleRemoteExceptionFromCarService(e);
876         }
877     }
878 
removeResourceOveruseListenerImpl()879     private void removeResourceOveruseListenerImpl() {
880         try {
881             mService.removeResourceOveruseListener(mResourceOveruseListenerImpl);
882             if (DEBUG) {
883                 Log.d(TAG, "Resource overuse listener implementation is successfully removed "
884                         + "from service");
885             }
886         } catch (RemoteException e) {
887             handleRemoteExceptionFromCarService(e);
888         }
889     }
890 
addResourceOveruseListenerForSystemImpl()891     private void addResourceOveruseListenerForSystemImpl() {
892         try {
893             mService.addResourceOveruseListenerForSystem(
894                     mResourceOveruseListenerForSystemImpl.resourceOveruseFlag(),
895                     mResourceOveruseListenerForSystemImpl);
896             if (DEBUG) {
897                 Log.d(TAG, "Resource overuse listener for system implementation is successfully "
898                         + "added to service");
899             }
900         } catch (RemoteException e) {
901             synchronized (mLock) {
902                 mResourceOveruseListenerForSystemInfos.clear();
903             }
904             handleRemoteExceptionFromCarService(e);
905         }
906     }
907 
removeResourceOveruseListenerForSystemImpl()908     private void removeResourceOveruseListenerForSystemImpl() {
909         try {
910             mService.removeResourceOveruseListenerForSystem(mResourceOveruseListenerForSystemImpl);
911             if (DEBUG) {
912                 Log.d(TAG, "Resource overuse listener for system implementation is successfully "
913                         + "removed from service");
914             }
915         } catch (RemoteException e) {
916             handleRemoteExceptionFromCarService(e);
917         }
918     }
919 
onResourceOveruse(ResourceOveruseStats resourceOveruseStats, boolean isSystem)920     private void onResourceOveruse(ResourceOveruseStats resourceOveruseStats, boolean isSystem) {
921         if (resourceOveruseStats.getIoOveruseStats() == null) {
922             Log.w(TAG, "Skipping resource overuse notification as the stats are missing");
923             return;
924         }
925         List<ResourceOveruseListenerInfo> listenerInfos;
926         synchronized (mLock) {
927             if (isSystem) {
928                 listenerInfos = mResourceOveruseListenerForSystemInfos;
929             } else {
930                 listenerInfos = mResourceOveruseListenerInfos;
931             }
932         }
933         if (listenerInfos.isEmpty()) {
934             Log.w(TAG, "Cannot notify resource overuse listener " + (isSystem ? "for system " : "")
935                     + "as it is not registered.");
936             return;
937         }
938         for (ResourceOveruseListenerInfo listenerInfo : listenerInfos) {
939             if ((listenerInfo.resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) == 1) {
940                 listenerInfo.executor.execute(() -> {
941                     listenerInfo.listener.onOveruse(resourceOveruseStats);
942                 });
943             }
944         }
945     }
946 
947     /** @hide */
948     private static final class ICarWatchdogClientImpl extends ICarWatchdogServiceCallback.Stub {
949         private final WeakReference<CarWatchdogManager> mManager;
950 
ICarWatchdogClientImpl(CarWatchdogManager manager)951         ICarWatchdogClientImpl(CarWatchdogManager manager) {
952             mManager = new WeakReference<>(manager);
953         }
954 
955         @Override
onCheckHealthStatus(int sessionId, int timeout)956         public void onCheckHealthStatus(int sessionId, int timeout) {
957             CarWatchdogManager manager = mManager.get();
958             if (manager != null) {
959                 manager.checkClientStatus(sessionId, timeout);
960             }
961         }
962 
963         @Override
onPrepareProcessTermination()964         public void onPrepareProcessTermination() {
965             CarWatchdogManager manager = mManager.get();
966             if (manager != null) {
967                 manager.notifyProcessTermination();
968             }
969         }
970     }
971 
972     private static final class SessionInfo {
973         public int currentId;
974         public int lastReportedId;
975 
SessionInfo(int currentId, int lastReportedId)976         SessionInfo(int currentId, int lastReportedId) {
977             this.currentId = currentId;
978             this.lastReportedId = lastReportedId;
979         }
980     }
981 
982     /** @hide */
983     private static final class IResourceOveruseListenerImpl extends IResourceOveruseListener.Stub {
984         private static final int[] RESOURCE_OVERUSE_FLAGS = new int[]{ FLAG_RESOURCE_OVERUSE_IO };
985 
986         private final WeakReference<CarWatchdogManager> mManager;
987         private final boolean mIsSystem;
988 
989         private final Object mLock = new Object();
990         @GuardedBy("mLock")
991         private final SparseIntArray mNumListenersByResource;
992 
993 
IResourceOveruseListenerImpl(CarWatchdogManager manager, boolean isSystem)994         IResourceOveruseListenerImpl(CarWatchdogManager manager, boolean isSystem) {
995             mManager = new WeakReference<>(manager);
996             mIsSystem = isSystem;
997             mNumListenersByResource = new SparseIntArray();
998         }
999 
1000         @Override
onOveruse(ResourceOveruseStats resourceOveruserStats)1001         public void onOveruse(ResourceOveruseStats resourceOveruserStats) {
1002             CarWatchdogManager manager = mManager.get();
1003             if (manager != null) {
1004                 manager.onResourceOveruse(resourceOveruserStats, mIsSystem);
1005             }
1006         }
1007 
hasListeners()1008         public boolean hasListeners() {
1009             synchronized (mLock) {
1010                 return mNumListenersByResource.size() != 0;
1011             }
1012         }
1013 
maybeAppendFlag(int appendFlag)1014         public boolean maybeAppendFlag(int appendFlag) {
1015             boolean isChanged = false;
1016             synchronized (mLock) {
1017                 for (int flag : RESOURCE_OVERUSE_FLAGS) {
1018                     if ((appendFlag & flag) != 1) {
1019                         continue;
1020                     }
1021                     int value = mNumListenersByResource.get(flag, 0);
1022                     isChanged = ++value == 1;
1023                     mNumListenersByResource.put(flag, value);
1024                 }
1025             }
1026             return isChanged;
1027         }
1028 
maybeRemoveFlag(int removeFlag)1029         public boolean maybeRemoveFlag(int removeFlag) {
1030             boolean isChanged = false;
1031             synchronized (mLock) {
1032                 for (int flag : RESOURCE_OVERUSE_FLAGS) {
1033                     if ((removeFlag & flag) != 1) {
1034                         continue;
1035                     }
1036                     int value = mNumListenersByResource.get(flag, 0);
1037                     if (value == 0) {
1038                         continue;
1039                     }
1040                     if (--value == 0) {
1041                         isChanged = true;
1042                         mNumListenersByResource.delete(flag);
1043                     } else {
1044                         mNumListenersByResource.put(flag, value);
1045                     }
1046                 }
1047             }
1048             return isChanged;
1049         }
1050 
resourceOveruseFlag()1051         public int resourceOveruseFlag() {
1052             int flag = 0;
1053             for (int i = 0; i < mNumListenersByResource.size(); ++i) {
1054                 flag |= mNumListenersByResource.valueAt(i) > 0 ? mNumListenersByResource.keyAt(i)
1055                         : 0;
1056             }
1057             return flag;
1058         }
1059     }
1060 
1061     /** @hide */
1062     private static final class ResourceOveruseListenerInfo {
1063         public final ResourceOveruseListener listener;
1064         public final Executor executor;
1065         public final int resourceOveruseFlag;
1066 
ResourceOveruseListenerInfo(ResourceOveruseListener listener, Executor executor, int resourceOveruseFlag)1067         ResourceOveruseListenerInfo(ResourceOveruseListener listener,
1068                 Executor executor, int resourceOveruseFlag) {
1069             this.listener = listener;
1070             this.executor = executor;
1071             this.resourceOveruseFlag = resourceOveruseFlag;
1072         }
1073 
1074         @Override
equals(Object obj)1075         public boolean equals(Object obj) {
1076             if (obj == this) {
1077                 return true;
1078             }
1079             if (!(obj instanceof ResourceOveruseListenerInfo)) {
1080                 return false;
1081             }
1082             ResourceOveruseListenerInfo listenerInfo = (ResourceOveruseListenerInfo) obj;
1083             return listenerInfo.listener == listener;
1084         }
1085     }
1086 }
1087