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 processIdentifier Process identifier of process that has 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, ProcessIdentifier processIdentifier)237     public void tellDumpFinished(ICarWatchdogMonitor monitor,
238             ProcessIdentifier processIdentifier) throws RemoteException {
239         invokeDaemonMethod((daemon) -> daemon.tellDumpFinished(monitor, processIdentifier));
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         invokeDaemonMethodForVersionAtLeast(
305                 (daemon) -> daemon.setThreadPriority(pid, tid, uid, policy, priority),
306                 /* expectedDaemonVersion= */ 2);
307     }
308 
309     /**
310      * Get the thread scheduling policy and priority.
311      *
312      * @param pid The process ID.
313      * @param tid The thread ID.
314      * @param uid The user ID for the thread.
315      */
getThreadPriority(int pid, int tid, int uid)316     public int[] getThreadPriority(int pid, int tid, int uid)
317             throws RemoteException {
318         // resultValues stores policy as first element and priority as second element.
319         int[] resultValues = new int[2];
320 
321         invokeDaemonMethodForVersionAtLeast((daemon) -> {
322             ThreadPolicyWithPriority t = daemon.getThreadPriority(pid, tid, uid);
323             resultValues[0] = t.policy;
324             resultValues[1] = t.priority;
325         }, /* expectedDaemonVersion= */ 2);
326 
327         return resultValues;
328     }
329 
330     /**
331      * Updates the daemon with the AIDL VHAL pid.
332      *
333      * This call is a response to the {@link ICarWatchdogServiceForSystem.Stub.requestAidlVhalPid}
334      * call.
335      *
336      * @param pid The AIDL VHAL process ID.
337      */
onAidlVhalPidFetched(int pid)338     public void onAidlVhalPidFetched(int pid) throws RemoteException {
339         invokeDaemonMethodForVersionAtLeast(
340                 (daemon) -> daemon.onAidlVhalPidFetched(pid), /* expectedDaemonVersion= */ 3);
341     }
342 
343     /**
344      * Handles the current UTC calendar day's I/O usage stats for all package collected during
345      * the previous boot.
346      *
347      * @param userPackageIoUsageStats I/O usage stats for all packages.
348      */
onTodayIoUsageStatsFetched(List<UserPackageIoUsageStats> userPackageIoUsageStats)349     public void onTodayIoUsageStatsFetched(List<UserPackageIoUsageStats> userPackageIoUsageStats)
350             throws RemoteException {
351         invokeDaemonMethodForVersionAtLeast(
352                 (daemon) -> daemon.onTodayIoUsageStatsFetched(userPackageIoUsageStats),
353                 /* expectedDaemonVersion= */ 3);
354     }
355 
invokeDaemonMethod(Invokable r)356     private void invokeDaemonMethod(Invokable r) throws RemoteException {
357         invokeDaemonMethodForVersionAtLeast(r, /* expectedDaemonVersion= */ -1);
358     }
359 
invokeDaemonMethodForVersionAtLeast(Invokable r, int expectedDaemonVersion)360     private void invokeDaemonMethodForVersionAtLeast(Invokable r, int expectedDaemonVersion)
361             throws RemoteException {
362         ICarWatchdog daemon;
363         synchronized (mLock) {
364             if (mCarWatchdogDaemon == null) {
365                 throw new IllegalStateException("Car watchdog daemon is not connected");
366             }
367             daemon = mCarWatchdogDaemon;
368         }
369         int actualDaemonVersion = daemon.getInterfaceVersion();
370         if (actualDaemonVersion < expectedDaemonVersion) {
371             // TODO(b/238328234): Replace this with a special exception type.
372             throw new UnsupportedOperationException(
373                     "Require car watchdog daemon version: " + expectedDaemonVersion
374                     + ", actual version: " + actualDaemonVersion);
375         }
376         r.invoke(daemon);
377     }
378 
connectToDaemon(int retryCount)379     private void connectToDaemon(int retryCount) {
380         if (retryCount <= 0) {
381             synchronized (mLock) {
382                 mConnectionInProgress = false;
383             }
384             Log.e(mTag, "Cannot reconnect to car watchdog daemon after retrying "
385                     + CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY + " times");
386             return;
387         }
388         if (makeBinderConnection()) {
389             Log.i(mTag, "Connected to car watchdog daemon");
390             return;
391         }
392         final int nextRetry = retryCount - 1;
393         mHandler.postDelayed(() -> connectToDaemon(nextRetry),
394                 CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
395     }
396 
makeBinderConnection()397     private boolean makeBinderConnection() {
398         long currentTimeMs = SystemClock.uptimeMillis();
399         IBinder binder = ServiceManagerHelper.checkService(CAR_WATCHDOG_DAEMON_INTERFACE);
400         if (binder == null) {
401             Log.w(mTag, "Getting car watchdog daemon binder failed");
402             return false;
403         }
404         long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs;
405         if (elapsedTimeMs > CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS) {
406             Log.wtf(mTag, "Finding car watchdog daemon took too long(" + elapsedTimeMs + "ms)");
407         }
408 
409         ICarWatchdog daemon = ICarWatchdog.Stub.asInterface(binder);
410         if (daemon == null) {
411             Log.w(mTag, "Getting car watchdog daemon interface failed");
412             return false;
413         }
414         synchronized (mLock) {
415             mCarWatchdogDaemon = daemon;
416             mConnectionInProgress = false;
417         }
418         linkToDeath();
419         for (OnConnectionChangeListener listener : mConnectionListeners) {
420             listener.onConnectionChange(/* isConnected= */true);
421         }
422         return true;
423     }
424 
linkToDeath()425     private void linkToDeath() {
426         IBinder binder;
427         synchronized (mLock) {
428             if (mCarWatchdogDaemon == null) {
429                 return;
430             }
431             binder = mCarWatchdogDaemon.asBinder();
432         }
433         if (binder == null) {
434             Log.w(mTag, "Linking to binder death recipient skipped");
435             return;
436         }
437         try {
438             binder.linkToDeath(mDeathRecipient, 0);
439         } catch (RemoteException e) {
440             Log.w(mTag, "Linking to binder death recipient failed: " + e);
441         }
442     }
443 
unlinkToDeath()444     private void unlinkToDeath() {
445         IBinder binder;
446         synchronized (mLock) {
447             if (mCarWatchdogDaemon == null) {
448                 return;
449             }
450             binder = mCarWatchdogDaemon.asBinder();
451         }
452         if (binder == null) {
453             Log.w(mTag, "Unlinking from binder death recipient skipped");
454             return;
455         }
456         binder.unlinkToDeath(mDeathRecipient, 0);
457     }
458 }
459