• 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 android.car.watchdog;
18 
19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
20 
21 import android.annotation.CallbackExecutor;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.car.Car;
27 import android.car.CarManagerBase;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.RemoteException;
32 import android.util.Log;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.util.Preconditions;
36 
37 import java.lang.annotation.ElementType;
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.lang.annotation.Target;
41 import java.lang.ref.WeakReference;
42 import java.util.concurrent.Executor;
43 
44 /**
45  * Provides APIs and interfaces for client health checking.
46  *
47  * @hide
48  */
49 @SystemApi
50 public final class CarWatchdogManager extends CarManagerBase {
51 
52     private static final String TAG = CarWatchdogManager.class.getSimpleName();
53     private static final boolean DEBUG = false; // STOPSHIP if true
54     private static final int INVALID_SESSION_ID = -1;
55     private static final int NUMBER_OF_CONDITIONS_TO_BE_MET = 2;
56     // Message ID representing main thread activeness checking.
57     private static final int WHAT_CHECK_MAIN_THREAD = 1;
58 
59     /** Timeout for services which should be responsive. The length is 3,000 milliseconds. */
60     public static final int TIMEOUT_CRITICAL = 0;
61 
62     /** Timeout for services which are relatively responsive. The length is 5,000 milliseconds. */
63     public static final int TIMEOUT_MODERATE = 1;
64 
65     /** Timeout for all other services. The length is 10,000 milliseconds. */
66     public static final int TIMEOUT_NORMAL = 2;
67 
68     /** @hide */
69     @Retention(RetentionPolicy.SOURCE)
70     @IntDef(prefix = "TIMEOUT_", value = {
71             TIMEOUT_CRITICAL,
72             TIMEOUT_MODERATE,
73             TIMEOUT_NORMAL,
74     })
75     @Target({ElementType.TYPE_USE})
76     public @interface TimeoutLengthEnum {}
77 
78     private final ICarWatchdogService mService;
79     private final ICarWatchdogClientImpl mClientImpl;
80     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
81 
82     private final Object mLock = new Object();
83     @GuardedBy("mLock")
84     private CarWatchdogClientCallback mRegisteredClient;
85     @GuardedBy("mLock")
86     private Executor mCallbackExecutor;
87     @GuardedBy("mLock")
88     private SessionInfo mSession = new SessionInfo(INVALID_SESSION_ID, INVALID_SESSION_ID);
89     @GuardedBy("mLock")
90     private int mRemainingConditions;
91 
92     /**
93      * CarWatchdogClientCallback is implemented by the clients which want to be health-checked by
94      * car watchdog server. Every time onCheckHealthStatus is called, they are expected to
95      * respond by calling {@link CarWatchdogManager.tellClientAlive} within timeout. If they don't
96      * respond, car watchdog server reports the current state and kills them.
97      *
98      * <p>Before car watchdog server kills the client, it calls onPrepareProcessTermination to allow
99      * them to prepare the termination. They will be killed in 1 second.
100      */
101     public abstract static class CarWatchdogClientCallback {
102         /**
103          * Car watchdog server pings the client to check if it is alive.
104          *
105          * <p>The callback method is called at the Executor which is specifed in {@link
106          * #registerClient}.
107          *
108          * @param sessionId Unique id to distinguish each health checking.
109          * @param timeout Time duration within which the client should respond.
110          *
111          * @return whether the response is immediately acknowledged. If {@code true}, car watchdog
112          *         server considers that the response is acknowledged already. If {@code false},
113          *         the client should call {@link CarWatchdogManager.tellClientAlive} later to tell
114          *         that it is alive.
115          */
onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout)116         public boolean onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout) {
117             return false;
118         }
119 
120         /**
121          * Car watchdog server notifies the client that it will be terminated in 1 second.
122          *
123          * <p>The callback method is called at the Executor which is specifed in {@link
124          * #registerClient}.
125          */
onPrepareProcessTermination()126         public void onPrepareProcessTermination() {}
127     }
128 
129     /** @hide */
CarWatchdogManager(Car car, IBinder service)130     public CarWatchdogManager(Car car, IBinder service) {
131         super(car);
132         mService = ICarWatchdogService.Stub.asInterface(service);
133         mClientImpl = new ICarWatchdogClientImpl(this);
134     }
135 
136     /**
137      * Registers the car watchdog clients to {@link CarWatchdogManager}.
138      *
139      * <p>It is allowed to register a client from any thread, but only one client can be
140      * registered. If two or more clients are needed, create a new {@link Car} and register a client
141      * to it.
142      *
143      * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
144      * @param timeout The time duration within which the client desires to respond. The actual
145      *        timeout is decided by watchdog server.
146      * @throws IllegalStateException if at least one client is already registered.
147      */
148     @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
registerClient(@onNull @allbackExecutor Executor executor, @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout)149     public void registerClient(@NonNull @CallbackExecutor Executor executor,
150             @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout) {
151         synchronized (mLock) {
152             if (mRegisteredClient == client) {
153                 return;
154             }
155             if (mRegisteredClient != null) {
156                 throw new IllegalStateException(
157                         "Cannot register the client. Only one client can be registered.");
158             }
159             mRegisteredClient = client;
160             mCallbackExecutor = executor;
161         }
162         try {
163             mService.registerClient(mClientImpl, timeout);
164             if (DEBUG) {
165                 Log.d(TAG, "Car watchdog client is successfully registered");
166             }
167         } catch (RemoteException e) {
168             synchronized (mLock) {
169                 mRegisteredClient = null;
170             }
171             handleRemoteExceptionFromCarService(e);
172         }
173     }
174 
175     /**
176      * Unregisters the car watchdog client from {@link CarWatchdogManager}.
177      *
178      * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
179      */
180     @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
unregisterClient(@onNull CarWatchdogClientCallback client)181     public void unregisterClient(@NonNull CarWatchdogClientCallback client) {
182         synchronized (mLock) {
183             if (mRegisteredClient != client) {
184                 Log.w(TAG, "Cannot unregister the client. It has not been registered.");
185                 return;
186             }
187             mRegisteredClient = null;
188             mCallbackExecutor = null;
189         }
190         try {
191             mService.unregisterClient(mClientImpl);
192             if (DEBUG) {
193                 Log.d(TAG, "Car watchdog client is successfully unregistered");
194             }
195         } catch (RemoteException e) {
196             handleRemoteExceptionFromCarService(e);
197         }
198     }
199 
200     /**
201      * Tells {@link CarWatchdogManager} that the client is alive.
202      *
203      * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
204      * @param sessionId Session id given by {@link CarWatchdogManager}.
205      * @throws IllegalStateException if {@code client} is not registered.
206      * @throws IllegalArgumentException if {@code session Id} is not correct.
207      */
208     @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG)
tellClientAlive(@onNull CarWatchdogClientCallback client, int sessionId)209     public void tellClientAlive(@NonNull CarWatchdogClientCallback client, int sessionId) {
210         boolean shouldReport;
211         synchronized (mLock) {
212             if (mRegisteredClient != client) {
213                 throw new IllegalStateException(
214                         "Cannot report client status. The client has not been registered.");
215             }
216             Preconditions.checkArgument(sessionId != -1 && mSession.currentId == sessionId,
217                     "Cannot report client status. "
218                     + "The given session id doesn't match the current one.");
219             if (mSession.lastReportedId == sessionId) {
220                 Log.w(TAG, "The given session id is already reported.");
221                 return;
222             }
223             mSession.lastReportedId = sessionId;
224             mRemainingConditions--;
225             shouldReport = checkConditionLocked();
226         }
227         if (shouldReport) {
228             reportToService(sessionId);
229         }
230     }
231 
232     /** @hide */
233     @Override
onCarDisconnected()234     public void onCarDisconnected() {
235         // nothing to do
236     }
237 
checkClientStatus(int sessionId, int timeout)238     private void checkClientStatus(int sessionId, int timeout) {
239         CarWatchdogClientCallback client;
240         Executor executor;
241         mMainHandler.removeMessages(WHAT_CHECK_MAIN_THREAD);
242         synchronized (mLock) {
243             if (mRegisteredClient == null) {
244                 Log.w(TAG, "Cannot check client status. The client has not been registered.");
245                 return;
246             }
247             mSession.currentId = sessionId;
248             client = mRegisteredClient;
249             executor = mCallbackExecutor;
250             mRemainingConditions = NUMBER_OF_CONDITIONS_TO_BE_MET;
251         }
252         // For a car watchdog client to be active, 1) its main thread is active and 2) the client
253         // responds within timeout. When each condition is met, the remaining task counter is
254         // decreased. If the remaining task counter is zero, the client is considered active.
255         mMainHandler.sendMessage(obtainMessage(CarWatchdogManager::checkMainThread, this)
256                 .setWhat(WHAT_CHECK_MAIN_THREAD));
257         // Call the client callback to check if the client is active.
258         executor.execute(() -> {
259             boolean checkDone = client.onCheckHealthStatus(sessionId, timeout);
260             if (checkDone) {
261                 boolean shouldReport;
262                 synchronized (mLock) {
263                     if (mSession.lastReportedId == sessionId) {
264                         return;
265                     }
266                     mSession.lastReportedId = sessionId;
267                     mRemainingConditions--;
268                     shouldReport = checkConditionLocked();
269                 }
270                 if (shouldReport) {
271                     reportToService(sessionId);
272                 }
273             }
274         });
275     }
276 
checkMainThread()277     private void checkMainThread() {
278         int sessionId;
279         boolean shouldReport;
280         synchronized (mLock) {
281             mRemainingConditions--;
282             sessionId = mSession.currentId;
283             shouldReport = checkConditionLocked();
284         }
285         if (shouldReport) {
286             reportToService(sessionId);
287         }
288     }
289 
checkConditionLocked()290     private boolean checkConditionLocked() {
291         if (mRemainingConditions < 0) {
292             Log.wtf(TAG, "Remaining condition is less than zero: should not happen");
293         }
294         return mRemainingConditions == 0;
295     }
296 
reportToService(int sessionId)297     private void reportToService(int sessionId) {
298         try {
299             mService.tellClientAlive(mClientImpl, sessionId);
300         } catch (RemoteException e) {
301             handleRemoteExceptionFromCarService(e);
302         }
303     }
304 
notifyProcessTermination()305     private void notifyProcessTermination() {
306         CarWatchdogClientCallback client;
307         Executor executor;
308         synchronized (mLock) {
309             if (mRegisteredClient == null) {
310                 Log.w(TAG, "Cannot notify the client. The client has not been registered.");
311                 return;
312             }
313             client = mRegisteredClient;
314             executor = mCallbackExecutor;
315         }
316         executor.execute(() -> client.onPrepareProcessTermination());
317     }
318 
319     /** @hide */
320     private static final class ICarWatchdogClientImpl extends ICarWatchdogServiceCallback.Stub {
321         private final WeakReference<CarWatchdogManager> mManager;
322 
ICarWatchdogClientImpl(CarWatchdogManager manager)323         private ICarWatchdogClientImpl(CarWatchdogManager manager) {
324             mManager = new WeakReference<>(manager);
325         }
326 
327         @Override
onCheckHealthStatus(int sessionId, int timeout)328         public void onCheckHealthStatus(int sessionId, int timeout) {
329             CarWatchdogManager manager = mManager.get();
330             if (manager != null) {
331                 manager.checkClientStatus(sessionId, timeout);
332             }
333         }
334 
335         @Override
onPrepareProcessTermination()336         public void onPrepareProcessTermination() {
337             CarWatchdogManager manager = mManager.get();
338             if (manager != null) {
339                 manager.notifyProcessTermination();
340             }
341         }
342     }
343 
344     private final class SessionInfo {
345         public int currentId;
346         public int lastReportedId;
347 
SessionInfo(int currentId, int lastReportedId)348         SessionInfo(int currentId, int lastReportedId) {
349             this.currentId = currentId;
350             this.lastReportedId = lastReportedId;
351         }
352     }
353 }
354