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