• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         }
109         try {
110             inactiveMainAfterInSec = Integer.parseInt(tokens[2]);
111         } catch (NumberFormatException e) {
112             throw new IllegalArgumentException("time for \"inactive main after\" is not number");
113         }
114         boolean verbose = false;
115         if (paramCount == 4) {
116             switch (tokens[3]) {
117                 case "true":
118                     verbose = true;
119                     break;
120                 case "false":
121                     verbose = false;
122                     break;
123                 default:
124                     throw new IllegalArgumentException("invalid verbose value: " + tokens[3]);
125             }
126         }
127         Log.i(TAG, "CarWatchdogClient command: timeout = " + tokens[0] + ", notRespondingAfter = "
128                 + notRespondAfterInSec + ", inactiveMainAfter = " + inactiveMainAfterInSec
129                 + ", verbose = " + verbose);
130         return new ClientConfig(timeout, inactiveMainAfterInSec, notRespondAfterInSec, verbose);
131     }
132 
CarWatchdogClient(Car car, ClientConfig config)133     private CarWatchdogClient(Car car, ClientConfig config) {
134         mClientConfig = config;
135         mCarWatchdogManager = (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE);
136         int clientHealthcheckIntervalSecs = SystemProperties.getInt(
137                 PROPERTY_RO_CLIENT_HEALTHCHECK_INTERVAL, MISSING_INT_PROPERTY_VALUE);
138         if (clientHealthcheckIntervalSecs != MISSING_INT_PROPERTY_VALUE) {
139             // Client must be inactive for at least twice the duration of the client health check
140             // window.
141             mOverriddenClientHealthCheckWindowMs = clientHealthcheckIntervalSecs * 2005L;
142             mOverriddenClientHealthCheckWindowMs = Math.max(mOverriddenClientHealthCheckWindowMs,
143                     getTimeForInactiveMain(CarWatchdogManager.TIMEOUT_NORMAL));
144         }
145     }
146 
registerAndGo()147     private void registerAndGo() {
148         mClientStartTime = SystemClock.uptimeMillis();
149         mCarWatchdogManager.registerClient(mCallbackExecutor, mClientCallback,
150                 mClientConfig.timeout);
151         // Post a runnable which takes long time to finish to the main thread if inactive_main_after
152         // is no less than 0
153         if (mClientConfig.inactiveMainAfterInMs >= 0) {
154             Handler handler = new Handler(Looper.getMainLooper());
155             handler.postDelayed(() -> {
156                 try {
157                     if (mClientConfig.verbose) {
158                         Log.i(TAG, "Main thread gets inactive");
159                     }
160                     Thread.sleep(getTimeForInactiveMain(mClientConfig.timeout));
161                 } catch (InterruptedException e) {
162                     // Ignore
163                 }
164             }, mClientConfig.inactiveMainAfterInMs);
165         }
166     }
167 
168     // The waiting time = (timeout * 2) + 50 milliseconds.
getTimeForInactiveMain(int timeout)169     private long getTimeForInactiveMain(int timeout) {
170         if (mOverriddenClientHealthCheckWindowMs != MISSING_INT_PROPERTY_VALUE) {
171             return mOverriddenClientHealthCheckWindowMs;
172         }
173         switch (timeout) {
174             case CarWatchdogManager.TIMEOUT_CRITICAL:
175                 return 6050L;
176             case CarWatchdogManager.TIMEOUT_MODERATE:
177                 return 10050L;
178             case CarWatchdogManager.TIMEOUT_NORMAL:
179                 return 20050L;
180             default:
181                 Log.w(TAG, "Invalid timeout");
182                 return 20050L;
183         }
184     }
185 
186     private static final class ClientConfig {
187         public int timeout;
188         public long inactiveMainAfterInMs;
189         public long notRespondAfterInMs;
190         public boolean verbose;
191 
ClientConfig(int timeout, int inactiveMainAfterInSec, int notRespondAfterInSec, boolean verbose)192         ClientConfig(int timeout, int inactiveMainAfterInSec, int notRespondAfterInSec,
193                 boolean verbose) {
194             this.timeout = timeout;
195             inactiveMainAfterInMs = inactiveMainAfterInSec * 1000L;
196             notRespondAfterInMs = notRespondAfterInSec * 1000L;
197             this.verbose = verbose;
198         }
199     }
200 }
201