Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 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.android.car.watchdog; |
| 18 | |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 19 | import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING; |
| 20 | import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 21 | import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL; |
| 22 | import static android.car.watchdog.CarWatchdogManager.TIMEOUT_MODERATE; |
| 23 | import static android.car.watchdog.CarWatchdogManager.TIMEOUT_NORMAL; |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 24 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 25 | import static com.android.car.CarLog.TAG_WATCHDOG; |
| 26 | import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; |
| 27 | |
| 28 | import android.annotation.NonNull; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 29 | import android.annotation.UserIdInt; |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 30 | import android.automotive.watchdog.ICarWatchdogClient; |
Eric Jeong | 27a178f | 2020-03-24 16:39:39 -0700 | [diff] [blame] | 31 | import android.automotive.watchdog.PowerCycle; |
| 32 | import android.automotive.watchdog.StateType; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 33 | import android.automotive.watchdog.UserState; |
Eric Jeong | 27a178f | 2020-03-24 16:39:39 -0700 | [diff] [blame] | 34 | import android.car.hardware.power.CarPowerManager.CarPowerStateListener; |
| 35 | import android.car.hardware.power.ICarPowerStateListener; |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 36 | import android.car.watchdog.ICarWatchdogService; |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 37 | import android.car.watchdoglib.CarWatchdogDaemonHelper; |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 38 | import android.content.Context; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 39 | import android.content.pm.UserInfo; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 40 | import android.os.Binder; |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 41 | import android.os.Handler; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 42 | import android.os.IBinder; |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 43 | import android.os.Looper; |
| 44 | import android.os.RemoteException; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 45 | import android.os.UserHandle; |
| 46 | import android.os.UserManager; |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 47 | import android.util.Log; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 48 | import android.util.SparseArray; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 49 | import android.util.SparseBooleanArray; |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 50 | |
Eric Jeong | 27a178f | 2020-03-24 16:39:39 -0700 | [diff] [blame] | 51 | import com.android.car.CarLocalServices; |
| 52 | import com.android.car.CarPowerManagementService; |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 53 | import com.android.car.CarServiceBase; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 54 | import com.android.car.user.CarUserService; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 55 | import com.android.internal.annotations.GuardedBy; |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 56 | import com.android.internal.annotations.VisibleForTesting; |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 57 | |
| 58 | import java.io.PrintWriter; |
| 59 | import java.lang.ref.WeakReference; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 60 | import java.util.ArrayList; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 61 | import java.util.List; |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 62 | |
| 63 | /** |
| 64 | * Service to implement CarWatchdogManager API. |
| 65 | * |
| 66 | * <p>CarWatchdogService runs as car watchdog mediator, which checks clients' health status and |
| 67 | * reports the result to car watchdog server. |
| 68 | */ |
| 69 | public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase { |
| 70 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 71 | private static final boolean DEBUG = true; // STOPSHIP if true (b/151474489) |
| 72 | private static final String TAG = TAG_WATCHDOG; |
| 73 | private static final int[] ALL_TIMEOUTS = |
| 74 | { TIMEOUT_CRITICAL, TIMEOUT_MODERATE, TIMEOUT_NORMAL }; |
| 75 | |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 76 | private final Context mContext; |
| 77 | private final ICarWatchdogClientImpl mWatchdogClient; |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 78 | private final Handler mMainHandler = new Handler(Looper.getMainLooper()); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 79 | private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper; |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 80 | private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener = |
| 81 | (connected) -> { |
| 82 | if (connected) { |
| 83 | registerToDaemon(); |
| 84 | } |
| 85 | }; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 86 | private final Object mLock = new Object(); |
| 87 | /* |
| 88 | * Keeps the list of car watchdog client according to timeout: |
| 89 | * key => timeout, value => ClientInfo list. |
| 90 | * The value of SparseArray is guarded by mLock. |
| 91 | */ |
| 92 | @GuardedBy("mLock") |
| 93 | private final SparseArray<ArrayList<ClientInfo>> mClientMap = new SparseArray<>(); |
| 94 | /* |
| 95 | * Keeps the map of car watchdog client being checked by CarWatchdogService according to |
| 96 | * timeout: key => timeout, value => ClientInfo map. |
| 97 | * The value is also a map: key => session id, value => ClientInfo. |
| 98 | */ |
| 99 | @GuardedBy("mLock") |
| 100 | private final SparseArray<SparseArray<ClientInfo>> mPingedClientMap = new SparseArray<>(); |
| 101 | /* |
| 102 | * Keeps whether client health checking is being performed according to timeout: |
| 103 | * key => timeout, value => boolean (whether client health checking is being performed). |
| 104 | * The value of SparseArray is guarded by mLock. |
| 105 | */ |
| 106 | @GuardedBy("mLock") |
| 107 | private final SparseArray<Boolean> mClientCheckInProgress = new SparseArray<>(); |
| 108 | @GuardedBy("mLock") |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 109 | private final ArrayList<ClientInfo> mClientsNotResponding = new ArrayList<>(); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 110 | @GuardedBy("mMainHandler") |
| 111 | private int mLastSessionId; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 112 | @GuardedBy("mMainHandler") |
| 113 | private final SparseBooleanArray mStoppedUser = new SparseBooleanArray(); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 114 | |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 115 | @VisibleForTesting |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 116 | public CarWatchdogService(Context context) { |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 117 | mContext = context; |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 118 | mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper(TAG_WATCHDOG); |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 119 | mWatchdogClient = new ICarWatchdogClientImpl(this); |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 120 | } |
| 121 | |
| 122 | @Override |
| 123 | public void init() { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 124 | for (int timeout : ALL_TIMEOUTS) { |
| 125 | mClientMap.put(timeout, new ArrayList<ClientInfo>()); |
| 126 | mPingedClientMap.put(timeout, new SparseArray<ClientInfo>()); |
| 127 | mClientCheckInProgress.put(timeout, false); |
| 128 | } |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 129 | subscribePowerCycleChange(); |
| 130 | subscribeUserStateChange(); |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 131 | mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener); |
| 132 | mCarWatchdogDaemonHelper.connect(); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 133 | if (DEBUG) { |
| 134 | Log.d(TAG, "CarWatchdogService is initialized"); |
| 135 | } |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | @Override |
| 139 | public void release() { |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 140 | unregisterFromDaemon(); |
| 141 | mCarWatchdogDaemonHelper.disconnect(); |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 142 | } |
| 143 | |
| 144 | @Override |
| 145 | public void dump(PrintWriter writer) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 146 | String indent = " "; |
| 147 | int count = 1; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 148 | synchronized (mLock) { |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 149 | writer.println("*CarWatchdogService*"); |
| 150 | writer.println("Registered clients"); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 151 | for (int timeout : ALL_TIMEOUTS) { |
| 152 | ArrayList<ClientInfo> clients = mClientMap.get(timeout); |
| 153 | String timeoutStr = timeoutToString(timeout); |
| 154 | for (int i = 0; i < clients.size(); i++, count++) { |
| 155 | ClientInfo clientInfo = clients.get(i); |
| 156 | writer.printf("%sclient #%d: timeout = %s, pid = %d\n", |
| 157 | indent, count, timeoutStr, clientInfo.pid); |
| 158 | } |
| 159 | } |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 160 | writer.printf("Stopped users: "); |
| 161 | int size = mStoppedUser.size(); |
| 162 | if (size > 0) { |
| 163 | writer.printf("%d", mStoppedUser.keyAt(0)); |
| 164 | for (int i = 1; i < size; i++) { |
| 165 | writer.printf(", %d", mStoppedUser.keyAt(i)); |
| 166 | } |
| 167 | writer.printf("\n"); |
| 168 | } else { |
| 169 | writer.printf("none\n"); |
| 170 | } |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 171 | } |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 172 | } |
| 173 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 174 | /** |
| 175 | * Registers {@link android.automotive.watchdog. ICarWatchdogClient} to |
| 176 | * {@link CarWatchdogService}. |
| 177 | */ |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 178 | @Override |
| 179 | public void registerClient(ICarWatchdogClient client, int timeout) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 180 | ArrayList<ClientInfo> clients = mClientMap.get(timeout); |
| 181 | if (clients == null) { |
| 182 | Log.w(TAG, "Cannot register the client: invalid timeout"); |
| 183 | return; |
| 184 | } |
| 185 | synchronized (mLock) { |
| 186 | IBinder binder = client.asBinder(); |
| 187 | for (int i = 0; i < clients.size(); i++) { |
| 188 | ClientInfo clientInfo = clients.get(i); |
| 189 | if (binder == clientInfo.client.asBinder()) { |
| 190 | Log.w(TAG, |
| 191 | "Cannot register the client: the client(pid:" + clientInfo.pid |
| 192 | + ") has been already registered"); |
| 193 | return; |
| 194 | } |
| 195 | } |
| 196 | int pid = Binder.getCallingPid(); |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 197 | int userId = UserHandle.getUserId(Binder.getCallingUid()); |
| 198 | ClientInfo clientInfo = new ClientInfo(client, pid, userId, timeout); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 199 | try { |
| 200 | clientInfo.linkToDeath(); |
| 201 | } catch (RemoteException e) { |
| 202 | Log.w(TAG, "Cannot register the client: linkToDeath to the client failed"); |
| 203 | return; |
| 204 | } |
| 205 | clients.add(clientInfo); |
| 206 | if (DEBUG) { |
| 207 | Log.d(TAG, "Client(pid: " + pid + ") is registered"); |
| 208 | } |
| 209 | } |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 210 | } |
| 211 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 212 | /** |
| 213 | * Unregisters {@link android.automotive.watchdog. ICarWatchdogClient} from |
| 214 | * {@link CarWatchdogService}. |
| 215 | */ |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 216 | @Override |
| 217 | public void unregisterClient(ICarWatchdogClient client) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 218 | synchronized (mLock) { |
| 219 | IBinder binder = client.asBinder(); |
| 220 | for (int timeout : ALL_TIMEOUTS) { |
| 221 | ArrayList<ClientInfo> clients = mClientMap.get(timeout); |
| 222 | for (int i = 0; i < clients.size(); i++) { |
| 223 | ClientInfo clientInfo = clients.get(i); |
| 224 | if (binder != clientInfo.client.asBinder()) { |
| 225 | continue; |
| 226 | } |
| 227 | clientInfo.unlinkToDeath(); |
| 228 | clients.remove(i); |
| 229 | if (DEBUG) { |
| 230 | Log.d(TAG, "Client(pid: " + clientInfo.pid + ") is unregistered"); |
| 231 | } |
| 232 | return; |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | Log.w(TAG, "Cannot unregister the client: the client has not been registered before"); |
| 237 | return; |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 238 | } |
| 239 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 240 | /** |
| 241 | * Tells {@link CarWatchdogService} that the client is alive. |
| 242 | */ |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 243 | @Override |
| 244 | public void tellClientAlive(ICarWatchdogClient client, int sessionId) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 245 | synchronized (mLock) { |
| 246 | for (int timeout : ALL_TIMEOUTS) { |
| 247 | if (!mClientCheckInProgress.get(timeout)) { |
| 248 | continue; |
| 249 | } |
| 250 | SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); |
| 251 | ClientInfo clientInfo = pingedClients.get(sessionId); |
| 252 | if (clientInfo != null && clientInfo.client.asBinder() == client.asBinder()) { |
| 253 | pingedClients.remove(sessionId); |
| 254 | return; |
| 255 | } |
| 256 | } |
| 257 | } |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 258 | } |
| 259 | |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 260 | @VisibleForTesting |
| 261 | protected int getClientCount(int timeout) { |
| 262 | synchronized (mLock) { |
| 263 | ArrayList<ClientInfo> clients = mClientMap.get(timeout); |
| 264 | return clients != null ? clients.size() : 0; |
| 265 | } |
| 266 | } |
| 267 | |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 268 | private void registerToDaemon() { |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 269 | try { |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 270 | mCarWatchdogDaemonHelper.registerMediator(mWatchdogClient); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 271 | if (DEBUG) { |
| 272 | Log.d(TAG, "CarWatchdogService registers to car watchdog daemon"); |
| 273 | } |
Eric Jeong | 4825ca3 | 2020-04-13 18:07:44 -0700 | [diff] [blame] | 274 | } catch (RemoteException | RuntimeException e) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 275 | Log.w(TAG, "Cannot register to car watchdog daemon: " + e); |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 276 | } |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 277 | UserManager userManager = UserManager.get(mContext); |
| 278 | List<UserInfo> users = userManager.getUsers(); |
| 279 | try { |
| 280 | // TODO(b/152780162): reduce the number of RPC calls(isUserRunning). |
| 281 | for (UserInfo info : users) { |
| 282 | int userState = userManager.isUserRunning(info.id) |
| 283 | ? UserState.USER_STATE_STARTED : UserState.USER_STATE_STOPPED; |
| 284 | mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE, info.id, |
| 285 | userState); |
| 286 | if (userState == UserState.USER_STATE_STOPPED) { |
| 287 | mStoppedUser.put(info.id, true); |
| 288 | } else { |
| 289 | mStoppedUser.delete(info.id); |
| 290 | } |
| 291 | } |
Eric Jeong | 4825ca3 | 2020-04-13 18:07:44 -0700 | [diff] [blame] | 292 | } catch (RemoteException | RuntimeException e) { |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 293 | Log.w(TAG, "Notifying system state change failed: " + e); |
| 294 | } |
Eric Jeong | 6df075f | 2020-03-11 16:12:23 -0700 | [diff] [blame] | 295 | } |
| 296 | |
| 297 | private void unregisterFromDaemon() { |
| 298 | try { |
| 299 | mCarWatchdogDaemonHelper.unregisterMediator(mWatchdogClient); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 300 | if (DEBUG) { |
| 301 | Log.d(TAG, "CarWatchdogService unregisters from car watchdog daemon"); |
| 302 | } |
Eric Jeong | 4825ca3 | 2020-04-13 18:07:44 -0700 | [diff] [blame] | 303 | } catch (RemoteException | RuntimeException e) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 304 | Log.w(TAG, "Cannot unregister from car watchdog daemon: " + e); |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | private void onClientDeath(ICarWatchdogClient client, int timeout) { |
| 309 | synchronized (mLock) { |
| 310 | removeClientLocked(client.asBinder(), timeout); |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 311 | } |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 312 | } |
| 313 | |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 314 | private void postHealthCheckMessage(int sessionId) { |
| 315 | mMainHandler.sendMessage(obtainMessage(CarWatchdogService::doHealthCheck, this, sessionId)); |
| 316 | } |
| 317 | |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 318 | private void doHealthCheck(int sessionId) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 319 | // For critical clients, the response status are checked just before reporting to car |
| 320 | // watchdog daemon. For moderate and normal clients, the status are checked after allowed |
| 321 | // delay per timeout. |
| 322 | analyzeClientResponse(TIMEOUT_CRITICAL); |
| 323 | reportHealthCheckResult(sessionId); |
| 324 | sendPingToClients(TIMEOUT_CRITICAL); |
| 325 | sendPingToClientsAndCheck(TIMEOUT_MODERATE); |
| 326 | sendPingToClientsAndCheck(TIMEOUT_NORMAL); |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 327 | } |
| 328 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 329 | private void analyzeClientResponse(int timeout) { |
| 330 | // Clients which are not responding are stored in mClientsNotResponding, and will be dumped |
| 331 | // and killed at the next response of CarWatchdogService to car watchdog daemon. |
| 332 | SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); |
| 333 | synchronized (mLock) { |
| 334 | for (int i = 0; i < pingedClients.size(); i++) { |
| 335 | ClientInfo clientInfo = pingedClients.valueAt(i); |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 336 | if (mStoppedUser.get(clientInfo.userId)) { |
| 337 | continue; |
| 338 | } |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 339 | mClientsNotResponding.add(clientInfo); |
| 340 | removeClientLocked(clientInfo.client.asBinder(), timeout); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 341 | } |
| 342 | mClientCheckInProgress.setValueAt(timeout, false); |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | private void sendPingToClients(int timeout) { |
| 347 | SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); |
| 348 | ArrayList<ClientInfo> clientsToCheck; |
| 349 | synchronized (mLock) { |
| 350 | pingedClients.clear(); |
| 351 | clientsToCheck = new ArrayList<>(mClientMap.get(timeout)); |
| 352 | for (int i = 0; i < clientsToCheck.size(); i++) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 353 | ClientInfo clientInfo = clientsToCheck.get(i); |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 354 | if (mStoppedUser.get(clientInfo.userId)) { |
| 355 | continue; |
| 356 | } |
| 357 | int sessionId = getNewSessionId(); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 358 | clientInfo.sessionId = sessionId; |
| 359 | pingedClients.put(sessionId, clientInfo); |
| 360 | } |
| 361 | mClientCheckInProgress.setValueAt(timeout, true); |
| 362 | } |
| 363 | for (int i = 0; i < clientsToCheck.size(); i++) { |
| 364 | ClientInfo clientInfo = clientsToCheck.get(i); |
| 365 | try { |
| 366 | clientInfo.client.checkIfAlive(clientInfo.sessionId, timeout); |
| 367 | } catch (RemoteException e) { |
| 368 | Log.w(TAG, "Sending a ping message to client(pid: " + clientInfo.pid |
| 369 | + ") failed: " + e); |
| 370 | synchronized (mLock) { |
| 371 | pingedClients.remove(clientInfo.sessionId); |
| 372 | } |
| 373 | } |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | private void sendPingToClientsAndCheck(int timeout) { |
| 378 | synchronized (mLock) { |
| 379 | if (mClientCheckInProgress.get(timeout)) { |
| 380 | return; |
| 381 | } |
| 382 | } |
| 383 | sendPingToClients(timeout); |
| 384 | mMainHandler.sendMessageDelayed(obtainMessage(CarWatchdogService::analyzeClientResponse, |
| 385 | this, timeout), timeoutToDurationMs(timeout)); |
| 386 | } |
| 387 | |
| 388 | private int getNewSessionId() { |
| 389 | if (++mLastSessionId <= 0) { |
| 390 | mLastSessionId = 1; |
| 391 | } |
| 392 | return mLastSessionId; |
| 393 | } |
| 394 | |
Eric Jeong | deb159f | 2020-03-23 17:20:05 -0700 | [diff] [blame] | 395 | private void removeClientLocked(IBinder clientBinder, int timeout) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 396 | ArrayList<ClientInfo> clients = mClientMap.get(timeout); |
| 397 | for (int i = 0; i < clients.size(); i++) { |
| 398 | ClientInfo clientInfo = clients.get(i); |
| 399 | if (clientBinder == clientInfo.client.asBinder()) { |
| 400 | clients.remove(i); |
Eric Jeong | deb159f | 2020-03-23 17:20:05 -0700 | [diff] [blame] | 401 | return; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 402 | } |
| 403 | } |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 404 | } |
| 405 | |
| 406 | private void reportHealthCheckResult(int sessionId) { |
| 407 | int[] clientsNotResponding; |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 408 | ArrayList<ClientInfo> clientsToNotify; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 409 | synchronized (mLock) { |
| 410 | clientsNotResponding = toIntArray(mClientsNotResponding); |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 411 | clientsToNotify = new ArrayList<>(mClientsNotResponding); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 412 | mClientsNotResponding.clear(); |
| 413 | } |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 414 | for (int i = 0; i < clientsToNotify.size(); i++) { |
| 415 | ClientInfo clientInfo = clientsToNotify.get(i); |
| 416 | try { |
| 417 | clientInfo.client.prepareProcessTermination(); |
| 418 | } catch (RemoteException e) { |
| 419 | Log.w(TAG, "Notifying prepareProcessTermination to client(pid: " + clientInfo.pid |
| 420 | + ") failed: " + e); |
| 421 | } |
| 422 | } |
| 423 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 424 | try { |
| 425 | mCarWatchdogDaemonHelper.tellMediatorAlive(mWatchdogClient, clientsNotResponding, |
| 426 | sessionId); |
Eric Jeong | 4825ca3 | 2020-04-13 18:07:44 -0700 | [diff] [blame] | 427 | } catch (RemoteException | RuntimeException e) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 428 | Log.w(TAG, "Cannot respond to car watchdog daemon (sessionId=" + sessionId + "): " + e); |
| 429 | } |
| 430 | } |
| 431 | |
Eric Jeong | 27a178f | 2020-03-24 16:39:39 -0700 | [diff] [blame] | 432 | private void subscribePowerCycleChange() { |
| 433 | CarPowerManagementService powerService = |
| 434 | CarLocalServices.getService(CarPowerManagementService.class); |
| 435 | if (powerService == null) { |
| 436 | Log.w(TAG, "Cannot get CarPowerManagementService"); |
| 437 | return; |
| 438 | } |
| 439 | powerService.registerListener(new ICarPowerStateListener.Stub() { |
| 440 | @Override |
| 441 | public void onStateChanged(int state) { |
| 442 | int powerCycle; |
| 443 | switch (state) { |
| 444 | // SHUTDOWN_PREPARE covers suspend and shutdown. |
| 445 | case CarPowerStateListener.SHUTDOWN_PREPARE: |
| 446 | powerCycle = PowerCycle.POWER_CYCLE_SUSPEND; |
| 447 | break; |
| 448 | // ON covers resume. |
| 449 | case CarPowerStateListener.ON: |
| 450 | powerCycle = PowerCycle.POWER_CYCLE_RESUME; |
| 451 | // There might be outdated & incorrect info. We should reset them before |
| 452 | // starting to do health check. |
| 453 | prepareHealthCheck(); |
| 454 | break; |
| 455 | default: |
| 456 | return; |
| 457 | } |
| 458 | try { |
| 459 | mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE, |
| 460 | powerCycle, /* arg2= */ -1); |
Eric Jeong | 4825ca3 | 2020-04-13 18:07:44 -0700 | [diff] [blame] | 461 | if (DEBUG) { |
| 462 | Log.d(TAG, "Notified car watchdog daemon a power cycle(" |
| 463 | + powerCycle + ")"); |
| 464 | } |
| 465 | } catch (RemoteException | RuntimeException e) { |
Eric Jeong | 27a178f | 2020-03-24 16:39:39 -0700 | [diff] [blame] | 466 | Log.w(TAG, "Notifying system state change failed: " + e); |
| 467 | } |
Eric Jeong | 27a178f | 2020-03-24 16:39:39 -0700 | [diff] [blame] | 468 | } |
| 469 | }); |
| 470 | } |
| 471 | |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 472 | private void subscribeUserStateChange() { |
| 473 | CarUserService userService = CarLocalServices.getService(CarUserService.class); |
| 474 | if (userService == null) { |
| 475 | Log.w(TAG, "Cannot get CarUserService"); |
| 476 | return; |
| 477 | } |
| 478 | userService.addUserLifecycleListener((event) -> { |
| 479 | int userId = event.getUserHandle().getIdentifier(); |
| 480 | int userState; |
| 481 | String userStateDesc; |
| 482 | synchronized (mLock) { |
| 483 | switch (event.getEventType()) { |
| 484 | case USER_LIFECYCLE_EVENT_TYPE_STARTING: |
| 485 | mStoppedUser.delete(userId); |
| 486 | userState = UserState.USER_STATE_STARTED; |
| 487 | userStateDesc = "STARTING"; |
| 488 | break; |
| 489 | case USER_LIFECYCLE_EVENT_TYPE_STOPPED: |
| 490 | mStoppedUser.put(userId, true); |
| 491 | userState = UserState.USER_STATE_STOPPED; |
| 492 | userStateDesc = "STOPPING"; |
| 493 | break; |
| 494 | default: |
| 495 | return; |
| 496 | } |
| 497 | } |
| 498 | try { |
| 499 | mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE, userId, |
| 500 | userState); |
Eric Jeong | 4825ca3 | 2020-04-13 18:07:44 -0700 | [diff] [blame] | 501 | if (DEBUG) { |
| 502 | Log.d(TAG, "Notified car watchdog daemon a user state: userId = " + userId |
| 503 | + ", userState = " + userStateDesc); |
| 504 | } |
| 505 | } catch (RemoteException | RuntimeException e) { |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 506 | Log.w(TAG, "Notifying system state change failed: " + e); |
| 507 | } |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 508 | }); |
| 509 | } |
| 510 | |
Eric Jeong | 27a178f | 2020-03-24 16:39:39 -0700 | [diff] [blame] | 511 | private void prepareHealthCheck() { |
| 512 | synchronized (mLock) { |
| 513 | for (int timeout : ALL_TIMEOUTS) { |
| 514 | SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); |
| 515 | pingedClients.clear(); |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 520 | @NonNull |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 521 | private int[] toIntArray(@NonNull ArrayList<ClientInfo> list) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 522 | int size = list.size(); |
| 523 | int[] intArray = new int[size]; |
| 524 | for (int i = 0; i < size; i++) { |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 525 | intArray[i] = list.get(i).pid; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 526 | } |
| 527 | return intArray; |
| 528 | } |
| 529 | |
| 530 | private String timeoutToString(int timeout) { |
| 531 | switch (timeout) { |
| 532 | case TIMEOUT_CRITICAL: |
| 533 | return "critical"; |
| 534 | case TIMEOUT_MODERATE: |
| 535 | return "moderate"; |
| 536 | case TIMEOUT_NORMAL: |
| 537 | return "normal"; |
| 538 | default: |
| 539 | Log.w(TAG, "Unknown timeout value"); |
| 540 | return "unknown"; |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | private long timeoutToDurationMs(int timeout) { |
| 545 | switch (timeout) { |
| 546 | case TIMEOUT_CRITICAL: |
| 547 | return 3000L; |
| 548 | case TIMEOUT_MODERATE: |
| 549 | return 5000L; |
| 550 | case TIMEOUT_NORMAL: |
| 551 | return 10000L; |
| 552 | default: |
| 553 | Log.w(TAG, "Unknown timeout value"); |
| 554 | return 10000L; |
| 555 | } |
| 556 | } |
| 557 | |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 558 | private static final class ICarWatchdogClientImpl extends ICarWatchdogClient.Stub { |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 559 | private final WeakReference<CarWatchdogService> mService; |
| 560 | |
| 561 | private ICarWatchdogClientImpl(CarWatchdogService service) { |
| 562 | mService = new WeakReference<>(service); |
| 563 | } |
| 564 | |
| 565 | @Override |
| 566 | public void checkIfAlive(int sessionId, int timeout) { |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 567 | CarWatchdogService service = mService.get(); |
| 568 | if (service == null) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 569 | Log.w(TAG, "CarWatchdogService is not available"); |
Eric Jeong | ae2c04c | 2020-02-21 09:18:31 -0800 | [diff] [blame] | 570 | return; |
| 571 | } |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 572 | service.postHealthCheckMessage(sessionId); |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 573 | } |
Jeongik Cha | 1d04bdd | 2020-04-08 22:19:52 +0900 | [diff] [blame] | 574 | |
| 575 | @Override |
Eric Jeong | 515009b | 2020-04-10 17:23:56 -0700 | [diff] [blame] | 576 | public void prepareProcessTermination() { |
| 577 | Log.w(TAG, "CarWatchdogService is about to be killed by car watchdog daemon"); |
| 578 | } |
| 579 | |
| 580 | @Override |
Jeongik Cha | 1d04bdd | 2020-04-08 22:19:52 +0900 | [diff] [blame] | 581 | public int getInterfaceVersion() { |
| 582 | return this.VERSION; |
| 583 | } |
| 584 | |
| 585 | @Override |
| 586 | public String getInterfaceHash() { |
| 587 | return this.HASH; |
| 588 | } |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 589 | } |
| 590 | |
| 591 | private final class ClientInfo implements IBinder.DeathRecipient { |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 592 | public final ICarWatchdogClient client; |
| 593 | public final int pid; |
| 594 | @UserIdInt public final int userId; |
| 595 | public final int timeout; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 596 | public volatile int sessionId; |
| 597 | |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 598 | private ClientInfo(ICarWatchdogClient client, int pid, @UserIdInt int userId, int timeout) { |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 599 | this.client = client; |
| 600 | this.pid = pid; |
Eric Jeong | 01a28aa | 2020-03-27 16:43:19 -0700 | [diff] [blame] | 601 | this.userId = userId; |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 602 | this.timeout = timeout; |
| 603 | } |
| 604 | |
Eric Jeong | 0ee7281 | 2020-04-21 15:23:54 -0700 | [diff] [blame^] | 605 | @Override |
| 606 | public void binderDied() { |
| 607 | Log.w(TAG, "Client(pid: " + pid + ") died"); |
| 608 | onClientDeath(client, timeout); |
| 609 | } |
| 610 | |
Eric Jeong | 0965756 | 2020-03-20 16:02:39 -0700 | [diff] [blame] | 611 | private void linkToDeath() throws RemoteException { |
| 612 | client.asBinder().linkToDeath(this, 0); |
| 613 | } |
| 614 | |
| 615 | private void unlinkToDeath() { |
| 616 | client.asBinder().unlinkToDeath(this, 0); |
| 617 | } |
Eric Jeong | 38ae821 | 2020-01-14 10:25:10 -0800 | [diff] [blame] | 618 | } |
| 619 | } |