• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.CarApiUtil;
24 import android.car.CarLibLog;
25 import android.car.CarManagerBase;
26 import android.car.CarNotConnectedException;
27 import android.car.diagnostic.ICarDiagnosticEventListener.Stub;
28 import android.content.Context;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.RemoteException;
32 import android.util.Log;
33 import android.util.SparseArray;
34 
35 import com.android.car.internal.CarPermission;
36 import com.android.car.internal.CarRatedListeners;
37 import com.android.car.internal.SingleMessageHandler;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.lang.ref.WeakReference;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.function.Consumer;
45 
46 /**
47  * API for monitoring car diagnostic data.
48  *
49  * @hide
50  */
51 @SystemApi
52 public 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 */
CarDiagnosticManager(IBinder service, Context context, Handler handler)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 */
onCarDisconnected()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          */
onDiagnosticEvent(final CarDiagnosticEvent carDiagnosticEvent)114         void onDiagnosticEvent(final CarDiagnosticEvent carDiagnosticEvent);
115     }
116 
117     // OnDiagnosticEventListener registration
118 
assertFrameType(@rameType int frameType)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      */
registerListener(OnDiagnosticEventListener listener, @FrameType int frameType, int rate)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      */
unregisterListener(OnDiagnosticEventListener listener)171     public void unregisterListener(OnDiagnosticEventListener listener) {
172         synchronized(mActiveListeners) {
173             for(@FrameType int frameType : FRAME_TYPES) {
174                 doUnregisterListenerLocked(listener, frameType);
175             }
176         }
177     }
178 
doUnregisterListenerLocked(OnDiagnosticEventListener listener, @FrameType int frameType)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 
registerOrUpdateDiagnosticListener(@rameType int frameType, int rate)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
getLatestLiveFrame()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      */
getFreezeFrameTimestamps()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
getFreezeFrame(long timestamp)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      */
clearFreezeFrames(long... timestamps)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      */
isLiveFrameSupported()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      */
isFreezeFrameNotificationSupported()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      */
isGetFreezeFrameSupported()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      */
isClearFreezeFramesSupported()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      */
isSelectiveClearFreezeFramesSupported()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 
CarDiagnosticEventListenerToService(CarDiagnosticManager manager)399         public CarDiagnosticEventListenerToService(CarDiagnosticManager manager) {
400             mManager = new WeakReference<>(manager);
401         }
402 
handleOnDiagnosticEvents(CarDiagnosticManager manager, List<CarDiagnosticEvent> events)403         private void handleOnDiagnosticEvents(CarDiagnosticManager manager,
404             List<CarDiagnosticEvent> events) {
405             manager.mHandlerCallback.sendEvents(events);
406         }
407 
408         @Override
onDiagnosticEvents(List<CarDiagnosticEvent> events)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> {
CarDiagnosticListeners(int rate)418         CarDiagnosticListeners(int rate) {
419             super(rate);
420         }
421 
onDiagnosticEvent(final CarDiagnosticEvent event)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