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