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