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