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 processIdentifiers List of process identifiers of processes that have 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, List<ProcessIdentifier> processIdentifiers)237 public void tellDumpFinished(ICarWatchdogMonitor monitor, 238 List<ProcessIdentifier> processIdentifiers) throws RemoteException { 239 invokeDaemonMethod((daemon) -> daemon.tellDumpFinished(monitor, processIdentifiers)); 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 invokeDaemonMethod((daemon) -> daemon.setThreadPriority(pid, tid, uid, policy, priority)); 305 } 306 307 /** 308 * Get the thread scheduling policy and priority. 309 * 310 * @param pid The process ID. 311 * @param tid The thread ID. 312 * @param uid The user ID for the thread. 313 */ getThreadPriority(int pid, int tid, int uid)314 public int[] getThreadPriority(int pid, int tid, int uid) 315 throws RemoteException { 316 // resultValues stores policy as first element and priority as second element. 317 int[] resultValues = new int[2]; 318 319 invokeDaemonMethod((daemon) -> { 320 ThreadPolicyWithPriority t = daemon.getThreadPriority(pid, tid, uid); 321 resultValues[0] = t.policy; 322 resultValues[1] = t.priority; 323 }); 324 325 return resultValues; 326 } 327 328 /** 329 * Updates the daemon with the AIDL VHAL pid. 330 * 331 * This call is a response to the {@link ICarWatchdogServiceForSystem.Stub.requestAidlVhalPid} 332 * call. 333 * 334 * @param pid The AIDL VHAL process ID. 335 */ onAidlVhalPidFetched(int pid)336 public void onAidlVhalPidFetched(int pid) throws RemoteException { 337 invokeDaemonMethod((daemon) -> daemon.onAidlVhalPidFetched(pid)); 338 } 339 340 /** 341 * Handles the current UTC calendar day's I/O usage stats for all package collected during 342 * the previous boot. 343 * 344 * @param userPackageIoUsageStats I/O usage stats for all packages. 345 */ onTodayIoUsageStatsFetched(List<UserPackageIoUsageStats> userPackageIoUsageStats)346 public void onTodayIoUsageStatsFetched(List<UserPackageIoUsageStats> userPackageIoUsageStats) 347 throws RemoteException { 348 invokeDaemonMethod((daemon) -> daemon.onTodayIoUsageStatsFetched(userPackageIoUsageStats)); 349 } 350 invokeDaemonMethod(Invokable r)351 private void invokeDaemonMethod(Invokable r) throws RemoteException { 352 ICarWatchdog daemon; 353 synchronized (mLock) { 354 if (mCarWatchdogDaemon == null) { 355 throw new IllegalStateException("Car watchdog daemon is not connected"); 356 } 357 daemon = mCarWatchdogDaemon; 358 } 359 r.invoke(daemon); 360 } 361 connectToDaemon(int retryCount)362 private void connectToDaemon(int retryCount) { 363 if (retryCount <= 0) { 364 synchronized (mLock) { 365 mConnectionInProgress = false; 366 } 367 Log.e(mTag, "Cannot reconnect to car watchdog daemon after retrying " 368 + CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY + " times"); 369 return; 370 } 371 if (makeBinderConnection()) { 372 Log.i(mTag, "Connected to car watchdog daemon"); 373 return; 374 } 375 final int nextRetry = retryCount - 1; 376 mHandler.postDelayed(() -> connectToDaemon(nextRetry), 377 CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS); 378 } 379 makeBinderConnection()380 private boolean makeBinderConnection() { 381 long currentTimeMs = SystemClock.uptimeMillis(); 382 IBinder binder = ServiceManagerHelper.checkService(CAR_WATCHDOG_DAEMON_INTERFACE); 383 if (binder == null) { 384 Log.w(mTag, "Getting car watchdog daemon binder failed"); 385 return false; 386 } 387 long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs; 388 if (elapsedTimeMs > CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS) { 389 Log.wtf(mTag, "Finding car watchdog daemon took too long(" + elapsedTimeMs + "ms)"); 390 } 391 392 ICarWatchdog daemon = ICarWatchdog.Stub.asInterface(binder); 393 if (daemon == null) { 394 Log.w(mTag, "Getting car watchdog daemon interface failed"); 395 return false; 396 } 397 synchronized (mLock) { 398 mCarWatchdogDaemon = daemon; 399 mConnectionInProgress = false; 400 } 401 linkToDeath(); 402 for (OnConnectionChangeListener listener : mConnectionListeners) { 403 listener.onConnectionChange(/* isConnected= */true); 404 } 405 return true; 406 } 407 linkToDeath()408 private void linkToDeath() { 409 IBinder binder; 410 synchronized (mLock) { 411 if (mCarWatchdogDaemon == null) { 412 return; 413 } 414 binder = mCarWatchdogDaemon.asBinder(); 415 } 416 if (binder == null) { 417 Log.w(mTag, "Linking to binder death recipient skipped"); 418 return; 419 } 420 try { 421 binder.linkToDeath(mDeathRecipient, 0); 422 } catch (RemoteException e) { 423 Log.w(mTag, "Linking to binder death recipient failed: " + e); 424 } 425 } 426 unlinkToDeath()427 private void unlinkToDeath() { 428 IBinder binder; 429 synchronized (mLock) { 430 if (mCarWatchdogDaemon == null) { 431 return; 432 } 433 binder = mCarWatchdogDaemon.asBinder(); 434 } 435 if (binder == null) { 436 Log.w(mTag, "Unlinking from binder death recipient skipped"); 437 return; 438 } 439 binder.unlinkToDeath(mDeathRecipient, 0); 440 } 441 } 442