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