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