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 com.google.android.car.kitchensink; 18 19 import android.annotation.NonNull; 20 import android.car.Car; 21 import android.car.watchdog.CarWatchdogManager; 22 import android.car.watchdog.CarWatchdogManager.CarWatchdogClientCallback; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.SystemClock; 26 import android.os.SystemProperties; 27 import android.util.Log; 28 29 import java.util.concurrent.ExecutorService; 30 import java.util.concurrent.Executors; 31 32 public final class CarWatchdogClient { 33 private static final String TAG = CarWatchdogClient.class.getSimpleName(); 34 private static final String TIMEOUT_CRITICAL = "critical"; 35 private static final String TIMEOUT_MODERATE = "moderate"; 36 private static final String TIMEOUT_NORMAL = "normal"; 37 private static final String PROPERTY_RO_CLIENT_HEALTHCHECK_INTERVAL = 38 "ro.carwatchdog.client_healthcheck.interval"; 39 private static final int MISSING_INT_PROPERTY_VALUE = -1; 40 41 private static CarWatchdogClient sCarWatchdogClient; 42 43 private final CarWatchdogManager mCarWatchdogManager; 44 private final CarWatchdogClientCallback mClientCallback = new CarWatchdogClientCallback() { 45 @Override 46 public boolean onCheckHealthStatus(int sessionId, int timeout) { 47 if (mClientConfig.verbose) { 48 Log.i(TAG, "onCheckHealthStatus: session id = " + sessionId); 49 } 50 long currentUptime = SystemClock.uptimeMillis(); 51 return mClientConfig.notRespondAfterInMs < 0 52 || mClientConfig.notRespondAfterInMs > currentUptime - mClientStartTime; 53 } 54 55 @Override 56 public void onPrepareProcessTermination() { 57 Log.w(TAG, "This process is being terminated by car watchdog"); 58 } 59 }; 60 private final ExecutorService mCallbackExecutor = Executors.newFixedThreadPool(1); 61 private ClientConfig mClientConfig; 62 private long mClientStartTime; 63 private long mOverriddenClientHealthCheckWindowMs = MISSING_INT_PROPERTY_VALUE; 64 65 // This method is not intended for multi-threaded calls. start(Car car, @NonNull String command)66 public static void start(Car car, @NonNull String command) { 67 if (sCarWatchdogClient != null) { 68 Log.w(TAG, "Car watchdog client already started"); 69 return; 70 } 71 ClientConfig config; 72 try { 73 config = parseCommand(command); 74 } catch (IllegalArgumentException e) { 75 Log.w(TAG, "Watchdog command error: " + e); 76 return; 77 } 78 sCarWatchdogClient = new CarWatchdogClient(car, config); 79 sCarWatchdogClient.registerAndGo(); 80 } 81 parseCommand(String command)82 private static ClientConfig parseCommand(String command) { 83 String[] tokens = command.split("[ ]+"); 84 int paramCount = tokens.length; 85 if (paramCount != 3 && paramCount != 4) { 86 throw new IllegalArgumentException("invalid command syntax"); 87 } 88 int timeout; 89 int inactiveMainAfterInSec; 90 int notRespondAfterInSec; 91 switch (tokens[0]) { 92 case TIMEOUT_CRITICAL: 93 timeout = CarWatchdogManager.TIMEOUT_CRITICAL; 94 break; 95 case TIMEOUT_MODERATE: 96 timeout = CarWatchdogManager.TIMEOUT_MODERATE; 97 break; 98 case TIMEOUT_NORMAL: 99 timeout = CarWatchdogManager.TIMEOUT_NORMAL; 100 break; 101 default: 102 throw new IllegalArgumentException("invalid timeout value"); 103 } 104 try { 105 notRespondAfterInSec = Integer.parseInt(tokens[1]); 106 } catch (NumberFormatException e) { 107 throw new IllegalArgumentException("time for \"not responding after\" is not number", 108 e); 109 } 110 try { 111 inactiveMainAfterInSec = Integer.parseInt(tokens[2]); 112 } catch (NumberFormatException e) { 113 throw new IllegalArgumentException("time for \"inactive main after\" is not number", e); 114 } 115 boolean verbose = false; 116 if (paramCount == 4) { 117 switch (tokens[3]) { 118 case "true": 119 verbose = true; 120 break; 121 case "false": 122 verbose = false; 123 break; 124 default: 125 throw new IllegalArgumentException("invalid verbose value: " + tokens[3]); 126 } 127 } 128 Log.i(TAG, "CarWatchdogClient command: timeout = " + tokens[0] + ", notRespondingAfter = " 129 + notRespondAfterInSec + ", inactiveMainAfter = " + inactiveMainAfterInSec 130 + ", verbose = " + verbose); 131 return new ClientConfig(timeout, inactiveMainAfterInSec, notRespondAfterInSec, verbose); 132 } 133 CarWatchdogClient(Car car, ClientConfig config)134 private CarWatchdogClient(Car car, ClientConfig config) { 135 mClientConfig = config; 136 mCarWatchdogManager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); 137 int clientHealthcheckIntervalSecs = SystemProperties.getInt( 138 PROPERTY_RO_CLIENT_HEALTHCHECK_INTERVAL, MISSING_INT_PROPERTY_VALUE); 139 if (clientHealthcheckIntervalSecs != MISSING_INT_PROPERTY_VALUE) { 140 // Client must be inactive for at least twice the duration of the client health check 141 // window. 142 mOverriddenClientHealthCheckWindowMs = clientHealthcheckIntervalSecs * 2005L; 143 mOverriddenClientHealthCheckWindowMs = Math.max(mOverriddenClientHealthCheckWindowMs, 144 getTimeForInactiveMain(CarWatchdogManager.TIMEOUT_NORMAL)); 145 } 146 } 147 registerAndGo()148 private void registerAndGo() { 149 mClientStartTime = SystemClock.uptimeMillis(); 150 mCarWatchdogManager.registerClient(mCallbackExecutor, mClientCallback, 151 mClientConfig.timeout); 152 // Post a runnable which takes long time to finish to the main thread if inactive_main_after 153 // is no less than 0 154 if (mClientConfig.inactiveMainAfterInMs >= 0) { 155 Handler handler = new Handler(Looper.getMainLooper()); 156 handler.postDelayed(() -> { 157 try { 158 if (mClientConfig.verbose) { 159 Log.i(TAG, "Main thread gets inactive"); 160 } 161 Thread.sleep(getTimeForInactiveMain(mClientConfig.timeout)); 162 } catch (InterruptedException e) { 163 // Ignore 164 } 165 }, mClientConfig.inactiveMainAfterInMs); 166 } 167 } 168 169 // The waiting time = (timeout * 2) + 50 milliseconds. getTimeForInactiveMain(int timeout)170 private long getTimeForInactiveMain(int timeout) { 171 if (mOverriddenClientHealthCheckWindowMs != MISSING_INT_PROPERTY_VALUE) { 172 return mOverriddenClientHealthCheckWindowMs; 173 } 174 switch (timeout) { 175 case CarWatchdogManager.TIMEOUT_CRITICAL: 176 return 6050L; 177 case CarWatchdogManager.TIMEOUT_MODERATE: 178 return 10050L; 179 case CarWatchdogManager.TIMEOUT_NORMAL: 180 return 20050L; 181 default: 182 Log.w(TAG, "Invalid timeout"); 183 return 20050L; 184 } 185 } 186 187 private static final class ClientConfig { 188 public int timeout; 189 public long inactiveMainAfterInMs; 190 public long notRespondAfterInMs; 191 public boolean verbose; 192 ClientConfig(int timeout, int inactiveMainAfterInSec, int notRespondAfterInSec, boolean verbose)193 ClientConfig(int timeout, int inactiveMainAfterInSec, int notRespondAfterInSec, 194 boolean verbose) { 195 this.timeout = timeout; 196 inactiveMainAfterInMs = inactiveMainAfterInSec * 1000L; 197 notRespondAfterInMs = notRespondAfterInSec * 1000L; 198 this.verbose = verbose; 199 } 200 } 201 } 202