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