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