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.hardware; 18 19 import android.car.Car; 20 import android.car.CarApiUtil; 21 import android.car.CarLibLog; 22 import android.car.CarManagerBase; 23 import android.car.CarNotConnectedException; 24 import android.content.Context; 25 import android.os.Handler; 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.ref.WeakReference; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.function.Consumer; 39 40 /** API for monitoring car diagnostic data. */ 41 /** @hide */ 42 public final class CarDiagnosticManager implements CarManagerBase { 43 public static final int FRAME_TYPE_FLAG_LIVE = 0; 44 public static final int FRAME_TYPE_FLAG_FREEZE = 1; 45 46 private static final int MSG_DIAGNOSTIC_EVENTS = 0; 47 48 private final ICarDiagnostic mService; 49 private final SparseArray<CarDiagnosticListeners> mActiveListeners = new SparseArray<>(); 50 51 /** Handles call back into clients. */ 52 private final SingleMessageHandler<CarDiagnosticEvent> mHandlerCallback; 53 54 private CarDiagnosticEventListenerToService mListenerToService; 55 56 private final CarPermission mVendorExtensionPermission; 57 58 public CarDiagnosticManager(IBinder service, Context context, Handler handler) { 59 mService = ICarDiagnostic.Stub.asInterface(service); 60 mHandlerCallback = new SingleMessageHandler<CarDiagnosticEvent>(handler.getLooper(), 61 MSG_DIAGNOSTIC_EVENTS) { 62 @Override 63 protected void handleEvent(CarDiagnosticEvent event) { 64 CarDiagnosticListeners listeners; 65 synchronized (mActiveListeners) { 66 listeners = mActiveListeners.get(event.frameType); 67 } 68 if (listeners != null) { 69 listeners.onDiagnosticEvent(event); 70 } 71 } 72 }; 73 mVendorExtensionPermission = new CarPermission(context, Car.PERMISSION_VENDOR_EXTENSION); 74 } 75 76 @Override 77 public void onCarDisconnected() { 78 synchronized(mActiveListeners) { 79 mActiveListeners.clear(); 80 mListenerToService = null; 81 } 82 } 83 84 /** Listener for diagnostic events. Callbacks are called in the Looper context. */ 85 public interface OnDiagnosticEventListener { 86 /** 87 * Called when there is a diagnostic event from the car. 88 * 89 * @param carDiagnosticEvent 90 */ 91 void onDiagnosticEvent(final CarDiagnosticEvent carDiagnosticEvent); 92 } 93 94 // OnDiagnosticEventListener registration 95 96 private void assertFrameType(int frameType) { 97 switch(frameType) { 98 case FRAME_TYPE_FLAG_FREEZE: 99 case FRAME_TYPE_FLAG_LIVE: 100 return; 101 default: 102 throw new IllegalArgumentException(String.format( 103 "%d is not a valid diagnostic frame type", frameType)); 104 } 105 } 106 107 /** 108 * Register a new listener for events of a given frame type and rate. 109 * @param listener 110 * @param frameType 111 * @param rate 112 * @return true if the registration was successful; false otherwise 113 * @throws CarNotConnectedException 114 * @throws IllegalArgumentException 115 */ 116 public boolean registerListener(OnDiagnosticEventListener listener, int frameType, int rate) 117 throws CarNotConnectedException, IllegalArgumentException { 118 assertFrameType(frameType); 119 synchronized(mActiveListeners) { 120 if (null == mListenerToService) { 121 mListenerToService = new CarDiagnosticEventListenerToService(this); 122 } 123 boolean needsServerUpdate = false; 124 CarDiagnosticListeners listeners = mActiveListeners.get(frameType); 125 if (listeners == null) { 126 listeners = new CarDiagnosticListeners(rate); 127 mActiveListeners.put(frameType, listeners); 128 needsServerUpdate = true; 129 } 130 if (listeners.addAndUpdateRate(listener, rate)) { 131 needsServerUpdate = true; 132 } 133 if (needsServerUpdate) { 134 if (!registerOrUpdateDiagnosticListener(frameType, rate)) { 135 return false; 136 } 137 } 138 } 139 return true; 140 } 141 142 /** 143 * Unregister a listener, causing it to stop receiving all diagnostic events. 144 * @param listener 145 */ 146 public void unregisterListener(OnDiagnosticEventListener listener) { 147 synchronized(mActiveListeners) { 148 for(int i = 0; i < mActiveListeners.size(); i++) { 149 doUnregisterListenerLocked(listener, mActiveListeners.keyAt(i)); 150 } 151 } 152 } 153 154 private void doUnregisterListenerLocked(OnDiagnosticEventListener listener, int sensor) { 155 CarDiagnosticListeners listeners = mActiveListeners.get(sensor); 156 if (listeners != null) { 157 boolean needsServerUpdate = false; 158 if (listeners.contains(listener)) { 159 needsServerUpdate = listeners.remove(listener); 160 } 161 if (listeners.isEmpty()) { 162 try { 163 mService.unregisterDiagnosticListener(sensor, 164 mListenerToService); 165 } catch (RemoteException e) { 166 //ignore 167 } 168 mActiveListeners.remove(sensor); 169 } else if (needsServerUpdate) { 170 try { 171 registerOrUpdateDiagnosticListener(sensor, listeners.getRate()); 172 } catch (CarNotConnectedException e) { 173 // ignore 174 } 175 } 176 } 177 } 178 179 private boolean registerOrUpdateDiagnosticListener(int frameType, int rate) 180 throws CarNotConnectedException { 181 try { 182 return mService.registerOrUpdateDiagnosticListener(frameType, rate, mListenerToService); 183 } catch (IllegalStateException e) { 184 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 185 } catch (RemoteException e) { 186 throw new CarNotConnectedException(); 187 } 188 return false; 189 } 190 191 // ICarDiagnostic forwards 192 193 /** 194 * Retrieve the most-recently acquired live frame data from the car. 195 * @return 196 * @throws CarNotConnectedException 197 */ 198 public CarDiagnosticEvent getLatestLiveFrame() throws CarNotConnectedException { 199 try { 200 return mService.getLatestLiveFrame(); 201 } catch (IllegalStateException e) { 202 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 203 } catch (RemoteException e) { 204 throw new CarNotConnectedException(); 205 } 206 return null; 207 } 208 209 /** 210 * Return the list of the timestamps for which a freeze frame is currently stored. 211 * @return 212 * @throws CarNotConnectedException 213 */ 214 public long[] getFreezeFrameTimestamps() throws CarNotConnectedException { 215 try { 216 return mService.getFreezeFrameTimestamps(); 217 } catch (IllegalStateException e) { 218 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 219 } catch (RemoteException e) { 220 throw new CarNotConnectedException(); 221 } 222 return new long[]{}; 223 } 224 225 /** 226 * Retrieve the freeze frame event data for a given timestamp, if available. 227 * @param timestamp 228 * @return 229 * @throws CarNotConnectedException 230 */ 231 public CarDiagnosticEvent getFreezeFrame(long timestamp) throws CarNotConnectedException { 232 try { 233 return mService.getFreezeFrame(timestamp); 234 } catch (IllegalStateException e) { 235 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 236 } catch (RemoteException e) { 237 throw new CarNotConnectedException(); 238 } 239 return null; 240 } 241 242 /** 243 * Clear the freeze frame information from vehicle memory at the given timestamps. 244 * @param timestamps 245 * @return 246 * @throws CarNotConnectedException 247 */ 248 public boolean clearFreezeFrames(long... timestamps) throws CarNotConnectedException { 249 try { 250 return mService.clearFreezeFrames(timestamps); 251 } catch (IllegalStateException e) { 252 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 253 } catch (RemoteException e) { 254 throw new CarNotConnectedException(); 255 } 256 return false; 257 } 258 259 private static class CarDiagnosticEventListenerToService 260 extends ICarDiagnosticEventListener.Stub { 261 private final WeakReference<CarDiagnosticManager> mManager; 262 263 public CarDiagnosticEventListenerToService(CarDiagnosticManager manager) { 264 mManager = new WeakReference<>(manager); 265 } 266 267 private void handleOnDiagnosticEvents(CarDiagnosticManager manager, 268 List<CarDiagnosticEvent> events) { 269 manager.mHandlerCallback.sendEvents(events); 270 } 271 272 @Override 273 public void onDiagnosticEvents(List<CarDiagnosticEvent> events) { 274 CarDiagnosticManager manager = mManager.get(); 275 if (manager != null) { 276 handleOnDiagnosticEvents(manager, events); 277 } 278 } 279 } 280 281 private class CarDiagnosticListeners extends CarRatedListeners<OnDiagnosticEventListener> { 282 CarDiagnosticListeners(int rate) { 283 super(rate); 284 } 285 286 void onDiagnosticEvent(final CarDiagnosticEvent event) { 287 // throw away old sensor data as oneway binder call can change order. 288 long updateTime = event.timestamp; 289 if (updateTime < mLastUpdateTime) { 290 Log.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old sensor data"); 291 return; 292 } 293 mLastUpdateTime = updateTime; 294 final boolean hasVendorExtensionPermission = mVendorExtensionPermission.checkGranted(); 295 final CarDiagnosticEvent eventToDispatch = hasVendorExtensionPermission ? 296 event : 297 event.withVendorSensorsRemoved(); 298 List<OnDiagnosticEventListener> listeners; 299 synchronized (mActiveListeners) { 300 listeners = new ArrayList<>(getListeners()); 301 } 302 listeners.forEach(new Consumer<OnDiagnosticEventListener>() { 303 304 @Override 305 public void accept(OnDiagnosticEventListener listener) { 306 listener.onDiagnosticEvent(eventToDispatch); 307 } 308 }); 309 } 310 } 311 } 312