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