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