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