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