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