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 static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.automotive.watchdog.internal.ICarWatchdog;
24 import android.automotive.watchdog.internal.ICarWatchdogMonitor;
25 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
26 import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Looper;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
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 = "carwatchdogd_system";
59 
60     private final Handler mHandler = new Handler(Looper.getMainLooper());
61     private final CopyOnWriteArrayList<OnConnectionChangeListener> mConnectionListeners =
62             new CopyOnWriteArrayList<>();
63     private final String mTag;
64     private final Object mLock = new Object();
65     @GuardedBy("mLock")
66     private @Nullable ICarWatchdog mCarWatchdogDaemon;
67     @GuardedBy("mLock")
68     private boolean mConnectionInProgress;
69 
70     private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
71         @Override
72         public void binderDied() {
73             Log.w(mTag, "Car watchdog daemon died: reconnecting");
74             unlinkToDeath();
75             synchronized (mLock) {
76                 mCarWatchdogDaemon = null;
77             }
78             for (OnConnectionChangeListener listener : mConnectionListeners) {
79                 listener.onConnectionChange(/* isConnected= */false);
80             }
81             mHandler.sendMessageDelayed(obtainMessage(CarWatchdogDaemonHelper::connectToDaemon,
82                     CarWatchdogDaemonHelper.this, 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 Array of process ID 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, int[] clientsNotResponding, int sessionId)219     public void tellCarWatchdogServiceAlive(
220             ICarWatchdogServiceForSystem service, int[] 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 pid ID 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, int pid)236     public void tellDumpFinished(ICarWatchdogMonitor monitor, int pid) throws RemoteException {
237         invokeDaemonMethod((daemon) -> daemon.tellDumpFinished(monitor, pid));
238     }
239 
240     /**
241      * Tells car watchdog daemon that system state has been changed for the specified StateType.
242      *
243      * @param type Either PowerCycle, UserState, or BootPhase
244      * @param arg1 First state change information for the specified state type.
245      * @param arg2 Second state change information for the specified state type.
246      * @throws IllegalArgumentException If the args don't match the state type. Refer to the aidl
247      *                                  interface for more information on the args.
248      * @throws IllegalStateException If car watchdog daemon is not connected.
249      * @throws RemoteException
250      */
notifySystemStateChange(int type, int arg1, int arg2)251     public void notifySystemStateChange(int type, int arg1, int arg2) throws RemoteException {
252         invokeDaemonMethod((daemon) -> daemon.notifySystemStateChange(type, arg1, arg2));
253     }
254 
255     /**
256      * Sets the given resource overuse configurations.
257      *
258      * @param configurations Resource overuse configuration per component type.
259      * @throws IllegalArgumentException If the configurations are invalid.
260      * @throws RemoteException
261      */
updateResourceOveruseConfigurations( List<ResourceOveruseConfiguration> configurations)262     public void updateResourceOveruseConfigurations(
263             List<ResourceOveruseConfiguration> configurations) throws RemoteException {
264         invokeDaemonMethod((daemon) -> daemon.updateResourceOveruseConfigurations(configurations));
265     }
266 
267     /**
268      * Returns the available resource overuse configurations.
269      *
270      * @throws RemoteException
271      */
getResourceOveruseConfigurations()272     public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations()
273             throws RemoteException {
274         List<ResourceOveruseConfiguration> configurations = new ArrayList<>();
275         invokeDaemonMethod((daemon) -> {
276             configurations.addAll(daemon.getResourceOveruseConfigurations());
277         });
278         return configurations;
279     }
280 
281     /**
282      * Enable/disable the internal client health check process.
283      * Disabling would stop the ANR killing process.
284      *
285      * @param disable True to disable watchdog's health check process.
286      */
controlProcessHealthCheck(boolean disable)287     public void controlProcessHealthCheck(boolean disable) throws RemoteException {
288         invokeDaemonMethod((daemon) -> daemon.controlProcessHealthCheck(disable));
289     }
290 
invokeDaemonMethod(Invokable r)291     private void invokeDaemonMethod(Invokable r) throws RemoteException {
292         ICarWatchdog daemon;
293         synchronized (mLock) {
294             if (mCarWatchdogDaemon == null) {
295                 throw new IllegalStateException("Car watchdog daemon is not connected");
296             }
297             daemon = mCarWatchdogDaemon;
298         }
299         r.invoke(daemon);
300     }
301 
connectToDaemon(int retryCount)302     private void connectToDaemon(int retryCount) {
303         if (retryCount <= 0) {
304             synchronized (mLock) {
305                 mConnectionInProgress = false;
306             }
307             Log.e(mTag, "Cannot reconnect to car watchdog daemon after retrying "
308                     + CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY + " times");
309             return;
310         }
311         if (makeBinderConnection()) {
312             Log.i(mTag, "Connected to car watchdog daemon");
313             return;
314         }
315         mHandler.sendMessageDelayed(obtainMessage(CarWatchdogDaemonHelper::connectToDaemon,
316                 CarWatchdogDaemonHelper.this, retryCount - 1),
317                 CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
318     }
319 
makeBinderConnection()320     private boolean makeBinderConnection() {
321         long currentTimeMs = SystemClock.uptimeMillis();
322         IBinder binder = ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE);
323         if (binder == null) {
324             Log.w(mTag, "Getting car watchdog daemon binder failed");
325             return false;
326         }
327         long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs;
328         if (elapsedTimeMs > CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS) {
329             Log.wtf(mTag, "Finding car watchdog daemon took too long(" + elapsedTimeMs + "ms)");
330         }
331 
332         ICarWatchdog daemon = ICarWatchdog.Stub.asInterface(binder);
333         if (daemon == null) {
334             Log.w(mTag, "Getting car watchdog daemon interface failed");
335             return false;
336         }
337         synchronized (mLock) {
338             mCarWatchdogDaemon = daemon;
339             mConnectionInProgress = false;
340         }
341         linkToDeath();
342         for (OnConnectionChangeListener listener : mConnectionListeners) {
343             listener.onConnectionChange(/* isConnected= */true);
344         }
345         return true;
346     }
347 
linkToDeath()348     private void linkToDeath() {
349         IBinder binder;
350         synchronized (mLock) {
351             if (mCarWatchdogDaemon == null) {
352                 return;
353             }
354             binder = mCarWatchdogDaemon.asBinder();
355         }
356         if (binder == null) {
357             Log.w(mTag, "Linking to binder death recipient skipped");
358             return;
359         }
360         try {
361             binder.linkToDeath(mDeathRecipient, 0);
362         } catch (RemoteException e) {
363             Log.w(mTag, "Linking to binder death recipient failed: " + e);
364         }
365     }
366 
unlinkToDeath()367     private void unlinkToDeath() {
368         IBinder binder;
369         synchronized (mLock) {
370             if (mCarWatchdogDaemon == null) {
371                 return;
372             }
373             binder = mCarWatchdogDaemon.asBinder();
374         }
375         if (binder == null) {
376             Log.w(mTag, "Unlinking from binder death recipient skipped");
377             return;
378         }
379         binder.unlinkToDeath(mDeathRecipient, 0);
380     }
381 }
382