• 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.watchdoglib;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.automotive.watchdog.internal.ICarWatchdog;
22 import android.automotive.watchdog.internal.ICarWatchdogMonitor;
23 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
24 import android.automotive.watchdog.internal.ProcessIdentifier;
25 import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
26 import android.automotive.watchdog.internal.ThreadPolicyWithPriority;
27 import android.automotive.watchdog.internal.UserPackageIoUsageStats;
28 import android.car.builtin.os.ServiceManagerHelper;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.SystemClock;
34 import android.util.Log;
35 
36 import com.android.internal.annotations.GuardedBy;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Objects;
41 import java.util.concurrent.CopyOnWriteArrayList;
42 
43 /**
44  * Helper class for car watchdog daemon.
45  *
46  * @hide
47  */
48 public final class CarWatchdogDaemonHelper {
49 
50     private static final String TAG = CarWatchdogDaemonHelper.class.getSimpleName();
51     /*
52      * Car watchdog daemon polls for the service manager status once every 250 milliseconds.
53      * CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS value should be at least twice the poll interval
54      * used by the daemon.
55      */
56     private static final long CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS = 500;
57     private static final long CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS = 300;
58     private static final int CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY = 3;
59     private static final String CAR_WATCHDOG_DAEMON_INTERFACE =
60             "android.automotive.watchdog.internal.ICarWatchdog/default";
61 
62     private final Handler mHandler = new Handler(Looper.getMainLooper());
63     private final CopyOnWriteArrayList<OnConnectionChangeListener> mConnectionListeners =
64             new CopyOnWriteArrayList<>();
65     private final String mTag;
66     private final Object mLock = new Object();
67     @GuardedBy("mLock")
68     private @Nullable ICarWatchdog mCarWatchdogDaemon;
69     @GuardedBy("mLock")
70     private boolean mConnectionInProgress;
71 
72     private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
73         @Override
74         public void binderDied() {
75             Log.w(mTag, "Car watchdog daemon died: reconnecting");
76             unlinkToDeath();
77             synchronized (mLock) {
78                 mCarWatchdogDaemon = null;
79             }
80             for (OnConnectionChangeListener listener : mConnectionListeners) {
81                 listener.onConnectionChange(/* isConnected= */false);
82             }
83             mHandler.postDelayed(() -> connectToDaemon(CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY),
84                     CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
85         }
86     };
87 
88     private interface Invokable {
invoke(ICarWatchdog daemon)89         void invoke(ICarWatchdog daemon) throws RemoteException;
90     }
91 
92     /**
93      * Listener to notify the state change of the connection to car watchdog daemon.
94      */
95     public interface OnConnectionChangeListener {
96         /** Gets called when car watchdog daemon is connected or disconnected. */
onConnectionChange(boolean isConnected)97         void onConnectionChange(boolean isConnected);
98     }
99 
CarWatchdogDaemonHelper()100     public CarWatchdogDaemonHelper() {
101         mTag = TAG;
102     }
103 
CarWatchdogDaemonHelper(@onNull String requestor)104     public CarWatchdogDaemonHelper(@NonNull String requestor) {
105         mTag = TAG + "[" + requestor + "]";
106     }
107 
108     /**
109      * Connects to car watchdog daemon.
110      *
111      * <p>When it's connected, {@link OnConnectionChangeListener} is called with
112      * {@code true}.
113      */
connect()114     public void connect() {
115         synchronized (mLock) {
116             if (mCarWatchdogDaemon != null || mConnectionInProgress) {
117                 return;
118             }
119             mConnectionInProgress = true;
120         }
121         connectToDaemon(CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY);
122     }
123 
124     /**
125      * Disconnects from car watchdog daemon.
126      *
127      * <p>When it's disconnected, {@link OnConnectionChangeListener} is called with
128      * {@code false}.
129      */
disconnect()130     public void disconnect() {
131         unlinkToDeath();
132         synchronized (mLock) {
133             mCarWatchdogDaemon = null;
134         }
135     }
136 
137     /**
138      * Adds {@link OnConnectionChangeListener}.
139      *
140      * @param listener Listener to be notified when connection state changes.
141      */
addOnConnectionChangeListener( @onNull OnConnectionChangeListener listener)142     public void addOnConnectionChangeListener(
143             @NonNull OnConnectionChangeListener listener) {
144         Objects.requireNonNull(listener, "Listener cannot be null");
145         mConnectionListeners.add(listener);
146     }
147 
148     /**
149      * Removes {@link OnConnectionChangeListener}.
150      *
151      * @param listener Listener to be removed.
152      */
removeOnConnectionChangeListener( @onNull OnConnectionChangeListener listener)153     public void removeOnConnectionChangeListener(
154             @NonNull OnConnectionChangeListener listener) {
155         Objects.requireNonNull(listener, "Listener cannot be null");
156         mConnectionListeners.remove(listener);
157     }
158 
159     /**
160      * Registers car watchdog service.
161      *
162      * @param service Car watchdog service to be registered.
163      * @throws IllegalArgumentException If the service is already registered.
164      * @throws IllegalStateException If car watchdog daemon is not connected.
165      * @throws RemoteException
166      */
registerCarWatchdogService( ICarWatchdogServiceForSystem service)167     public void registerCarWatchdogService(
168             ICarWatchdogServiceForSystem service) throws RemoteException {
169         invokeDaemonMethod((daemon) -> daemon.registerCarWatchdogService(service));
170     }
171 
172     /**
173      * Unregisters car watchdog service.
174      *
175      * @param service Car watchdog service to be unregistered.
176      * @throws IllegalArgumentException If the service is not registered.
177      * @throws IllegalStateException If car watchdog daemon is not connected.
178      * @throws RemoteException
179      */
unregisterCarWatchdogService( ICarWatchdogServiceForSystem service)180     public void unregisterCarWatchdogService(
181             ICarWatchdogServiceForSystem service)  throws RemoteException {
182         invokeDaemonMethod((daemon) -> daemon.unregisterCarWatchdogService(service));
183     }
184 
185     /**
186      * Registers car watchdog monitor.
187      *
188      * @param monitor Car watchdog monitor to be registered.
189      * @throws IllegalArgumentException If there is another monitor registered.
190      * @throws IllegalStateException If car watchdog daemon is not connected.
191      * @throws RemoteException
192      */
registerMonitor(ICarWatchdogMonitor monitor)193     public void registerMonitor(ICarWatchdogMonitor monitor) throws RemoteException {
194         invokeDaemonMethod((daemon) -> daemon.registerMonitor(monitor));
195     }
196 
197     /**
198      * Unregisters car watchdog monitor.
199      *
200      * @param monitor Car watchdog monitor to be unregistered.
201      * @throws IllegalArgumentException If the monitor is not registered.
202      * @throws IllegalStateException If car watchdog daemon is not connected.
203      * @throws RemoteException
204      */
unregisterMonitor(ICarWatchdogMonitor monitor)205     public void unregisterMonitor(ICarWatchdogMonitor monitor) throws RemoteException {
206         invokeDaemonMethod((daemon) -> daemon.unregisterMonitor(monitor));
207     }
208 
209     /**
210      * Tells car watchdog daemon that the service is alive.
211      *
212      * @param service Car watchdog service which has been pined by car watchdog daemon.
213      * @param clientsNotResponding List of process identifiers of clients that are not responding.
214      * @param sessionId Session ID that car watchdog daemon has given.
215      * @throws IllegalArgumentException If the service is not registered,
216      *                                  or session ID is not correct.
217      * @throws IllegalStateException If car watchdog daemon is not connected.
218      * @throws RemoteException
219      */
tellCarWatchdogServiceAlive( ICarWatchdogServiceForSystem service, List<ProcessIdentifier> clientsNotResponding, int sessionId)220     public void tellCarWatchdogServiceAlive(
221             ICarWatchdogServiceForSystem service, List<ProcessIdentifier> clientsNotResponding,
222             int sessionId) throws RemoteException {
223         invokeDaemonMethod(
224                 (daemon) -> daemon.tellCarWatchdogServiceAlive(
225                     service, clientsNotResponding, sessionId));
226     }
227 
228     /**
229      * Tells car watchdog daemon that the monitor has dumped clients' process information.
230      *
231      * @param monitor Car watchdog monitor that dumped process information.
232      * @param processIdentifiers List of process identifiers of processes that have been dumped.
233      * @throws IllegalArgumentException If the monitor is not registered.
234      * @throws IllegalStateException If car watchdog daemon is not connected.
235      * @throws RemoteException
236      */
tellDumpFinished(ICarWatchdogMonitor monitor, List<ProcessIdentifier> processIdentifiers)237     public void tellDumpFinished(ICarWatchdogMonitor monitor,
238             List<ProcessIdentifier> processIdentifiers) throws RemoteException {
239         invokeDaemonMethod((daemon) -> daemon.tellDumpFinished(monitor, processIdentifiers));
240     }
241 
242     /**
243      * Tells car watchdog daemon that system state has been changed for the specified StateType.
244      *
245      * @param type Either PowerCycle, UserState, or BootPhase
246      * @param arg1 First state change information for the specified state type.
247      * @param arg2 Second state change information for the specified state type.
248      * @throws IllegalArgumentException If the args don't match the state type. Refer to the aidl
249      *                                  interface for more information on the args.
250      * @throws IllegalStateException If car watchdog daemon is not connected.
251      * @throws RemoteException
252      */
notifySystemStateChange(int type, int arg1, int arg2)253     public void notifySystemStateChange(int type, int arg1, int arg2) throws RemoteException {
254         invokeDaemonMethod((daemon) -> daemon.notifySystemStateChange(type, arg1, arg2));
255     }
256 
257     /**
258      * Sets the given resource overuse configurations.
259      *
260      * @param configurations Resource overuse configuration per component type.
261      * @throws IllegalArgumentException If the configurations are invalid.
262      * @throws RemoteException
263      */
updateResourceOveruseConfigurations( List<ResourceOveruseConfiguration> configurations)264     public void updateResourceOveruseConfigurations(
265             List<ResourceOveruseConfiguration> configurations) throws RemoteException {
266         invokeDaemonMethod((daemon) -> daemon.updateResourceOveruseConfigurations(configurations));
267     }
268 
269     /**
270      * Returns the available resource overuse configurations.
271      *
272      * @throws RemoteException
273      */
getResourceOveruseConfigurations()274     public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations()
275             throws RemoteException {
276         List<ResourceOveruseConfiguration> configurations = new ArrayList<>();
277         invokeDaemonMethod((daemon) -> {
278             configurations.addAll(daemon.getResourceOveruseConfigurations());
279         });
280         return configurations;
281     }
282 
283     /**
284      * Enable/disable the internal client health check process.
285      * Disabling would stop the ANR killing process.
286      *
287      * @param enable True to enable watchdog's health check process.
288      */
controlProcessHealthCheck(boolean enable)289     public void controlProcessHealthCheck(boolean enable) throws RemoteException {
290         invokeDaemonMethod((daemon) -> daemon.controlProcessHealthCheck(enable));
291     }
292 
293     /**
294      * Set the thread scheduling policy and priority.
295      *
296      * @param pid The process ID.
297      * @param tid The thread ID.
298      * @param uid The user ID for the thread.
299      * @param policy The scheduling policy.
300      * @param priority The scheduling priority.
301      */
setThreadPriority(int pid, int tid, int uid, int policy, int priority)302     public void setThreadPriority(int pid, int tid, int uid, int policy, int priority)
303             throws RemoteException {
304         invokeDaemonMethod((daemon) -> daemon.setThreadPriority(pid, tid, uid, policy, priority));
305     }
306 
307     /**
308      * Get the thread scheduling policy and priority.
309      *
310      * @param pid The process ID.
311      * @param tid The thread ID.
312      * @param uid The user ID for the thread.
313      */
getThreadPriority(int pid, int tid, int uid)314     public int[] getThreadPriority(int pid, int tid, int uid)
315             throws RemoteException {
316         // resultValues stores policy as first element and priority as second element.
317         int[] resultValues = new int[2];
318 
319         invokeDaemonMethod((daemon) -> {
320             ThreadPolicyWithPriority t = daemon.getThreadPriority(pid, tid, uid);
321             resultValues[0] = t.policy;
322             resultValues[1] = t.priority;
323         });
324 
325         return resultValues;
326     }
327 
328     /**
329      * Updates the daemon with the AIDL VHAL pid.
330      *
331      * This call is a response to the {@link ICarWatchdogServiceForSystem.Stub.requestAidlVhalPid}
332      * call.
333      *
334      * @param pid The AIDL VHAL process ID.
335      */
onAidlVhalPidFetched(int pid)336     public void onAidlVhalPidFetched(int pid) throws RemoteException {
337         invokeDaemonMethod((daemon) -> daemon.onAidlVhalPidFetched(pid));
338     }
339 
340     /**
341      * Handles the current UTC calendar day's I/O usage stats for all package collected during
342      * the previous boot.
343      *
344      * @param userPackageIoUsageStats I/O usage stats for all packages.
345      */
onTodayIoUsageStatsFetched(List<UserPackageIoUsageStats> userPackageIoUsageStats)346     public void onTodayIoUsageStatsFetched(List<UserPackageIoUsageStats> userPackageIoUsageStats)
347             throws RemoteException {
348         invokeDaemonMethod((daemon) -> daemon.onTodayIoUsageStatsFetched(userPackageIoUsageStats));
349     }
350 
invokeDaemonMethod(Invokable r)351     private void invokeDaemonMethod(Invokable r) throws RemoteException {
352         ICarWatchdog daemon;
353         synchronized (mLock) {
354             if (mCarWatchdogDaemon == null) {
355                 throw new IllegalStateException("Car watchdog daemon is not connected");
356             }
357             daemon = mCarWatchdogDaemon;
358         }
359         r.invoke(daemon);
360     }
361 
connectToDaemon(int retryCount)362     private void connectToDaemon(int retryCount) {
363         if (retryCount <= 0) {
364             synchronized (mLock) {
365                 mConnectionInProgress = false;
366             }
367             Log.e(mTag, "Cannot reconnect to car watchdog daemon after retrying "
368                     + CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY + " times");
369             return;
370         }
371         if (makeBinderConnection()) {
372             Log.i(mTag, "Connected to car watchdog daemon");
373             return;
374         }
375         final int nextRetry = retryCount - 1;
376         mHandler.postDelayed(() -> connectToDaemon(nextRetry),
377                 CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
378     }
379 
makeBinderConnection()380     private boolean makeBinderConnection() {
381         long currentTimeMs = SystemClock.uptimeMillis();
382         IBinder binder = ServiceManagerHelper.checkService(CAR_WATCHDOG_DAEMON_INTERFACE);
383         if (binder == null) {
384             Log.w(mTag, "Getting car watchdog daemon binder failed");
385             return false;
386         }
387         long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs;
388         if (elapsedTimeMs > CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS) {
389             Log.wtf(mTag, "Finding car watchdog daemon took too long(" + elapsedTimeMs + "ms)");
390         }
391 
392         ICarWatchdog daemon = ICarWatchdog.Stub.asInterface(binder);
393         if (daemon == null) {
394             Log.w(mTag, "Getting car watchdog daemon interface failed");
395             return false;
396         }
397         synchronized (mLock) {
398             mCarWatchdogDaemon = daemon;
399             mConnectionInProgress = false;
400         }
401         linkToDeath();
402         for (OnConnectionChangeListener listener : mConnectionListeners) {
403             listener.onConnectionChange(/* isConnected= */true);
404         }
405         return true;
406     }
407 
linkToDeath()408     private void linkToDeath() {
409         IBinder binder;
410         synchronized (mLock) {
411             if (mCarWatchdogDaemon == null) {
412                 return;
413             }
414             binder = mCarWatchdogDaemon.asBinder();
415         }
416         if (binder == null) {
417             Log.w(mTag, "Linking to binder death recipient skipped");
418             return;
419         }
420         try {
421             binder.linkToDeath(mDeathRecipient, 0);
422         } catch (RemoteException e) {
423             Log.w(mTag, "Linking to binder death recipient failed: " + e);
424         }
425     }
426 
unlinkToDeath()427     private void unlinkToDeath() {
428         IBinder binder;
429         synchronized (mLock) {
430             if (mCarWatchdogDaemon == null) {
431                 return;
432             }
433             binder = mCarWatchdogDaemon.asBinder();
434         }
435         if (binder == null) {
436             Log.w(mTag, "Unlinking from binder death recipient skipped");
437             return;
438         }
439         binder.unlinkToDeath(mDeathRecipient, 0);
440     }
441 }
442