1/* 2 * Copyright (C) 2017 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 17package android.car.diagnostic; 18 19import android.annotation.IntDef; 20import android.annotation.Nullable; 21import android.annotation.SystemApi; 22import android.car.Car; 23import android.car.CarApiUtil; 24import android.car.CarLibLog; 25import android.car.CarManagerBase; 26import android.car.CarNotConnectedException; 27import android.car.diagnostic.ICarDiagnosticEventListener.Stub; 28import android.content.Context; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.RemoteException; 32import android.util.Log; 33import android.util.SparseArray; 34 35import com.android.car.internal.CarPermission; 36import com.android.car.internal.CarRatedListeners; 37import com.android.car.internal.SingleMessageHandler; 38 39import java.lang.annotation.Retention; 40import java.lang.annotation.RetentionPolicy; 41import java.lang.ref.WeakReference; 42import java.util.ArrayList; 43import java.util.List; 44import java.util.function.Consumer; 45 46/** 47 * API for monitoring car diagnostic data. 48 * 49 * @hide 50 */ 51@SystemApi 52public final class CarDiagnosticManager implements CarManagerBase { 53 public static final int FRAME_TYPE_LIVE = 0; 54 public static final int FRAME_TYPE_FREEZE = 1; 55 56 @Retention(RetentionPolicy.SOURCE) 57 @IntDef({FRAME_TYPE_LIVE, FRAME_TYPE_FREEZE}) 58 /** @hide */ 59 public @interface FrameType {} 60 61 /** @hide */ 62 public static final @FrameType int FRAME_TYPES[] = { 63 FRAME_TYPE_LIVE, 64 FRAME_TYPE_FREEZE 65 }; 66 67 private static final int MSG_DIAGNOSTIC_EVENTS = 0; 68 69 private final ICarDiagnostic mService; 70 private final SparseArray<CarDiagnosticListeners> mActiveListeners = new SparseArray<>(); 71 72 /** Handles call back into clients. */ 73 private final SingleMessageHandler<CarDiagnosticEvent> mHandlerCallback; 74 75 private CarDiagnosticEventListenerToService mListenerToService; 76 77 private final CarPermission mVendorExtensionPermission; 78 79 /** @hide */ 80 public CarDiagnosticManager(IBinder service, Context context, Handler handler) { 81 mService = ICarDiagnostic.Stub.asInterface(service); 82 mHandlerCallback = new SingleMessageHandler<CarDiagnosticEvent>(handler.getLooper(), 83 MSG_DIAGNOSTIC_EVENTS) { 84 @Override 85 protected void handleEvent(CarDiagnosticEvent event) { 86 CarDiagnosticListeners listeners; 87 synchronized (mActiveListeners) { 88 listeners = mActiveListeners.get(event.frameType); 89 } 90 if (listeners != null) { 91 listeners.onDiagnosticEvent(event); 92 } 93 } 94 }; 95 mVendorExtensionPermission = new CarPermission(context, Car.PERMISSION_VENDOR_EXTENSION); 96 } 97 98 @Override 99 /** @hide */ 100 public void onCarDisconnected() { 101 synchronized(mActiveListeners) { 102 mActiveListeners.clear(); 103 mListenerToService = null; 104 } 105 } 106 107 /** Listener for diagnostic events. Callbacks are called in the Looper context. */ 108 public interface OnDiagnosticEventListener { 109 /** 110 * Called when there is a diagnostic event from the car. 111 * 112 * @param carDiagnosticEvent 113 */ 114 void onDiagnosticEvent(final CarDiagnosticEvent carDiagnosticEvent); 115 } 116 117 // OnDiagnosticEventListener registration 118 119 private void assertFrameType(@FrameType int frameType) { 120 switch(frameType) { 121 case FRAME_TYPE_FREEZE: 122 case FRAME_TYPE_LIVE: 123 return; 124 default: 125 throw new IllegalArgumentException(String.format( 126 "%d is not a valid diagnostic frame type", frameType)); 127 } 128 } 129 130 /** 131 * Register a new listener for events of a given frame type and rate. 132 * @param listener 133 * @param frameType 134 * @param rate 135 * @return true if the registration was successful; false otherwise 136 * @throws CarNotConnectedException 137 * @throws IllegalArgumentException 138 */ 139 public boolean registerListener(OnDiagnosticEventListener listener, 140 @FrameType int frameType, 141 int rate) 142 throws CarNotConnectedException, IllegalArgumentException { 143 assertFrameType(frameType); 144 synchronized(mActiveListeners) { 145 if (null == mListenerToService) { 146 mListenerToService = new CarDiagnosticEventListenerToService(this); 147 } 148 boolean needsServerUpdate = false; 149 CarDiagnosticListeners listeners = mActiveListeners.get(frameType); 150 if (listeners == null) { 151 listeners = new CarDiagnosticListeners(rate); 152 mActiveListeners.put(frameType, listeners); 153 needsServerUpdate = true; 154 } 155 if (listeners.addAndUpdateRate(listener, rate)) { 156 needsServerUpdate = true; 157 } 158 if (needsServerUpdate) { 159 if (!registerOrUpdateDiagnosticListener(frameType, rate)) { 160 return false; 161 } 162 } 163 } 164 return true; 165 } 166 167 /** 168 * Unregister a listener, causing it to stop receiving all diagnostic events. 169 * @param listener 170 */ 171 public void unregisterListener(OnDiagnosticEventListener listener) { 172 synchronized(mActiveListeners) { 173 for(@FrameType int frameType : FRAME_TYPES) { 174 doUnregisterListenerLocked(listener, frameType); 175 } 176 } 177 } 178 179 private void doUnregisterListenerLocked(OnDiagnosticEventListener listener, 180 @FrameType int frameType) { 181 CarDiagnosticListeners listeners = mActiveListeners.get(frameType); 182 if (listeners != null) { 183 boolean needsServerUpdate = false; 184 if (listeners.contains(listener)) { 185 needsServerUpdate = listeners.remove(listener); 186 } 187 if (listeners.isEmpty()) { 188 try { 189 mService.unregisterDiagnosticListener(frameType, 190 mListenerToService); 191 } catch (RemoteException e) { 192 //ignore 193 } 194 mActiveListeners.remove(frameType); 195 } else if (needsServerUpdate) { 196 try { 197 registerOrUpdateDiagnosticListener(frameType, listeners.getRate()); 198 } catch (CarNotConnectedException e) { 199 // ignore 200 } 201 } 202 } 203 } 204 205 private boolean registerOrUpdateDiagnosticListener(@FrameType int frameType, int rate) 206 throws CarNotConnectedException { 207 try { 208 return mService.registerOrUpdateDiagnosticListener(frameType, rate, mListenerToService); 209 } catch (IllegalStateException e) { 210 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 211 } catch (RemoteException e) { 212 throw new CarNotConnectedException(); 213 } 214 return false; 215 } 216 217 // ICarDiagnostic forwards 218 219 /** 220 * Retrieve the most-recently acquired live frame data from the car. 221 * @return A CarDiagnostic event for the most recently known live frame if one is present. 222 * null if no live frame has been recorded by the vehicle. 223 * @throws CarNotConnectedException 224 */ 225 public @Nullable 226 CarDiagnosticEvent getLatestLiveFrame() throws CarNotConnectedException { 227 try { 228 return mService.getLatestLiveFrame(); 229 } catch (IllegalStateException e) { 230 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 231 } catch (RemoteException e) { 232 throw new CarNotConnectedException(); 233 } 234 return null; 235 } 236 237 /** 238 * Return the list of the timestamps for which a freeze frame is currently stored. 239 * @return An array containing timestamps at which, at the current time, the vehicle has 240 * a freeze frame stored. If no freeze frames are currently stored, an empty 241 * array will be returned. 242 * Because vehicles might have a limited amount of storage for frames, clients cannot 243 * assume that a timestamp obtained via this call will be indefinitely valid for retrieval 244 * of the actual diagnostic data, and must be prepared to handle a missing frame. 245 * @throws CarNotConnectedException 246 */ 247 public long[] getFreezeFrameTimestamps() throws CarNotConnectedException { 248 try { 249 return mService.getFreezeFrameTimestamps(); 250 } catch (IllegalStateException e) { 251 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 252 } catch (RemoteException e) { 253 throw new CarNotConnectedException(); 254 } 255 return new long[]{}; 256 } 257 258 /** 259 * Retrieve the freeze frame event data for a given timestamp, if available. 260 * @param timestamp 261 * @return A CarDiagnostic event for the frame at the given timestamp, if one is 262 * available. null is returned otherwise. 263 * Storage constraints might cause frames to be deleted from vehicle memory. 264 * For this reason it cannot be assumed that a timestamp will yield a valid frame, 265 * even if it was initially obtained via a call to getFreezeFrameTimestamps(). 266 * @throws CarNotConnectedException 267 */ 268 public @Nullable 269 CarDiagnosticEvent getFreezeFrame(long timestamp) 270 throws CarNotConnectedException { 271 try { 272 return mService.getFreezeFrame(timestamp); 273 } catch (IllegalStateException e) { 274 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 275 } catch (RemoteException e) { 276 throw new CarNotConnectedException(); 277 } 278 return null; 279 } 280 281 /** 282 * Clear the freeze frame information from vehicle memory at the given timestamps. 283 * @param timestamps A list of timestamps to delete freeze frames at, or an empty array 284 * to delete all freeze frames from vehicle memory. 285 * @return true if all the required frames were deleted (including if no timestamps are 286 * provided and all frames were cleared); false otherwise. 287 * Due to storage constraints, timestamps cannot be assumed to be indefinitely valid, and 288 * a false return from this method should be used by the client as cause for invalidating 289 * its local knowledge of the vehicle diagnostic state. 290 * @throws CarNotConnectedException 291 */ 292 public boolean clearFreezeFrames(long... timestamps) throws CarNotConnectedException { 293 try { 294 return mService.clearFreezeFrames(timestamps); 295 } catch (IllegalStateException e) { 296 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 297 } catch (RemoteException e) { 298 throw new CarNotConnectedException(); 299 } 300 return false; 301 } 302 303 /** 304 * Returns true if this vehicle supports sending live frame information. 305 * @return 306 * @throws CarNotConnectedException 307 */ 308 public boolean isLiveFrameSupported() throws CarNotConnectedException { 309 try { 310 return mService.isLiveFrameSupported(); 311 } catch (IllegalStateException e) { 312 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 313 } catch (RemoteException e) { 314 throw new CarNotConnectedException(); 315 } 316 return false; 317 } 318 319 /** 320 * Returns true if this vehicle supports supports sending notifications to 321 * registered listeners when new freeze frames happen. 322 * @throws CarNotConnectedException 323 */ 324 public boolean isFreezeFrameNotificationSupported() throws CarNotConnectedException { 325 try { 326 return mService.isFreezeFrameNotificationSupported(); 327 } catch (IllegalStateException e) { 328 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 329 } catch (RemoteException e) { 330 throw new CarNotConnectedException(); 331 } 332 return false; 333 } 334 335 /** 336 * Returns whether the underlying HAL supports retrieving freeze frames 337 * stored in vehicle memory using timestamp. 338 * @throws CarNotConnectedException 339 */ 340 public boolean isGetFreezeFrameSupported() throws CarNotConnectedException { 341 try { 342 return mService.isGetFreezeFrameSupported(); 343 } catch (IllegalStateException e) { 344 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 345 } catch (RemoteException e) { 346 throw new CarNotConnectedException(); 347 } 348 return false; 349 } 350 351 /** 352 * Returns true if this vehicle supports clearing all freeze frames. 353 * This is only meaningful if freeze frame data is also supported. 354 * 355 * A return value of true for this method indicates that it is supported to call 356 * carDiagnosticManager.clearFreezeFrames() 357 * to delete all freeze frames stored in vehicle memory. 358 * 359 * @return 360 * @throws CarNotConnectedException 361 */ 362 public boolean isClearFreezeFramesSupported() throws CarNotConnectedException { 363 try { 364 return mService.isClearFreezeFramesSupported(); 365 } catch (IllegalStateException e) { 366 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 367 } catch (RemoteException e) { 368 throw new CarNotConnectedException(); 369 } 370 return false; 371 } 372 373 /** 374 * Returns true if this vehicle supports clearing specific freeze frames by timestamp. 375 * This is only meaningful if freeze frame data is also supported. 376 * 377 * A return value of true for this method indicates that it is supported to call 378 * carDiagnosticManager.clearFreezeFrames(timestamp1, timestamp2, ...) 379 * to delete the freeze frames stored for the provided input timestamps, provided any exist. 380 * 381 * @return 382 * @throws CarNotConnectedException 383 */ 384 public boolean isSelectiveClearFreezeFramesSupported() throws CarNotConnectedException { 385 try { 386 return mService.isSelectiveClearFreezeFramesSupported(); 387 } catch (IllegalStateException e) { 388 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 389 } catch (RemoteException e) { 390 throw new CarNotConnectedException(); 391 } 392 return false; 393 } 394 395 private static class CarDiagnosticEventListenerToService 396 extends Stub { 397 private final WeakReference<CarDiagnosticManager> mManager; 398 399 public CarDiagnosticEventListenerToService(CarDiagnosticManager manager) { 400 mManager = new WeakReference<>(manager); 401 } 402 403 private void handleOnDiagnosticEvents(CarDiagnosticManager manager, 404 List<CarDiagnosticEvent> events) { 405 manager.mHandlerCallback.sendEvents(events); 406 } 407 408 @Override 409 public void onDiagnosticEvents(List<CarDiagnosticEvent> events) { 410 CarDiagnosticManager manager = mManager.get(); 411 if (manager != null) { 412 handleOnDiagnosticEvents(manager, events); 413 } 414 } 415 } 416 417 private class CarDiagnosticListeners extends CarRatedListeners<OnDiagnosticEventListener> { 418 CarDiagnosticListeners(int rate) { 419 super(rate); 420 } 421 422 void onDiagnosticEvent(final CarDiagnosticEvent event) { 423 // throw away old data as oneway binder call can change order. 424 long updateTime = event.timestamp; 425 if (updateTime < mLastUpdateTime) { 426 Log.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old data"); 427 return; 428 } 429 mLastUpdateTime = updateTime; 430 final boolean hasVendorExtensionPermission = mVendorExtensionPermission.checkGranted(); 431 final CarDiagnosticEvent eventToDispatch = hasVendorExtensionPermission ? 432 event : 433 event.withVendorSensorsRemoved(); 434 List<OnDiagnosticEventListener> listeners; 435 synchronized (mActiveListeners) { 436 listeners = new ArrayList<>(getListeners()); 437 } 438 listeners.forEach(new Consumer<OnDiagnosticEventListener>() { 439 440 @Override 441 public void accept(OnDiagnosticEventListener listener) { 442 listener.onDiagnosticEvent(eventToDispatch); 443 } 444 }); 445 } 446 } 447} 448