blob: 7341940a3a65869808cf99c180b8ac9dc528ee24 [file] [log] [blame]
Steve Paik4d257022018-04-27 13:28:31 -07001/*
2 * Copyright (C) 2018 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
17package com.android.car;
18
19import static java.lang.Integer.toHexString;
20
Kaiafbb5022019-10-21 14:24:07 -070021import android.car.Car;
Steve Paik4d257022018-04-27 13:28:31 -070022import android.car.hardware.CarPropertyConfig;
23import android.car.hardware.CarPropertyValue;
24import android.car.hardware.property.CarPropertyEvent;
25import android.car.hardware.property.ICarProperty;
26import android.car.hardware.property.ICarPropertyEventListener;
27import android.content.Context;
Kaid5d8b922020-05-12 23:07:45 -070028import android.os.Handler;
29import android.os.HandlerThread;
Steve Paik4d257022018-04-27 13:28:31 -070030import android.os.IBinder;
31import android.os.RemoteException;
Felipe Leme176a5fd2021-01-20 15:48:33 -080032import android.util.IndentingPrintWriter;
Steve Paik4d257022018-04-27 13:28:31 -070033import android.util.Pair;
Eric Jeongbd5fb562020-12-21 13:49:40 -080034import android.util.Slog;
Steve Paik4d257022018-04-27 13:28:31 -070035import android.util.SparseArray;
36
37import com.android.car.hal.PropertyHalService;
Kai6e75d492020-02-20 16:11:40 -080038import com.android.internal.annotations.GuardedBy;
Steve Paik4d257022018-04-27 13:28:31 -070039
Steve Paik4d257022018-04-27 13:28:31 -070040import java.util.ArrayList;
41import java.util.HashMap;
Kai7a4e6ea2020-06-10 02:25:37 -070042import java.util.HashSet;
Steve Paik4d257022018-04-27 13:28:31 -070043import java.util.LinkedList;
44import java.util.List;
45import java.util.Map;
Kai7a4e6ea2020-06-10 02:25:37 -070046import java.util.Set;
Steve Paik4d257022018-04-27 13:28:31 -070047import java.util.concurrent.ConcurrentHashMap;
48import java.util.concurrent.CopyOnWriteArrayList;
49
50/**
51 * This class implements the binder interface for ICarProperty.aidl to make it easier to create
Kai3a8a5af2020-03-11 15:39:58 -070052 * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in
53 * manager level.
Steve Paik4d257022018-04-27 13:28:31 -070054 */
55public class CarPropertyService extends ICarProperty.Stub
56 implements CarServiceBase, PropertyHalService.PropertyHalListener {
57 private static final boolean DBG = true;
Mayank Garg72c71d22021-02-03 23:54:45 -080058 private static final String TAG = CarLog.tagFor(CarPropertyService.class);
Steve Paik4d257022018-04-27 13:28:31 -070059 private final Context mContext;
60 private final Map<IBinder, Client> mClientMap = new ConcurrentHashMap<>();
Kai7a4e6ea2020-06-10 02:25:37 -070061 @GuardedBy("mLock")
62 private final Map<Integer, CarPropertyConfig<?>> mConfigs = new HashMap<>();
Steve Paik4d257022018-04-27 13:28:31 -070063 private final PropertyHalService mHal;
64 private boolean mListenerIsSet = false;
65 private final Map<Integer, List<Client>> mPropIdClientMap = new ConcurrentHashMap<>();
66 private final Object mLock = new Object();
Kai6e75d492020-02-20 16:11:40 -080067 @GuardedBy("mLock")
68 private final SparseArray<SparseArray<Client>> mSetOperationClientMap = new SparseArray<>();
Kaid5d8b922020-05-12 23:07:45 -070069 private final HandlerThread mHandlerThread =
70 CarServiceUtils.getHandlerThread(getClass().getSimpleName());
71 private final Handler mHandler = new Handler(mHandlerThread.getLooper());
72
Steve Paik4d257022018-04-27 13:28:31 -070073 public CarPropertyService(Context context, PropertyHalService hal) {
74 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -080075 Slog.d(TAG, "CarPropertyService started!");
Steve Paik4d257022018-04-27 13:28:31 -070076 }
77 mHal = hal;
78 mContext = context;
79 }
80
81 // Helper class to keep track of listeners to this service
82 private class Client implements IBinder.DeathRecipient {
83 private final ICarPropertyEventListener mListener;
84 private final IBinder mListenerBinder;
85 private final SparseArray<Float> mRateMap = new SparseArray<Float>(); // key is propId
86
87 Client(ICarPropertyEventListener listener) {
88 mListener = listener;
89 mListenerBinder = listener.asBinder();
90
91 try {
92 mListenerBinder.linkToDeath(this, 0);
93 } catch (RemoteException e) {
Justin Paupore0dd31772019-02-07 21:22:34 -080094 throw new IllegalStateException("Client already dead", e);
Steve Paik4d257022018-04-27 13:28:31 -070095 }
96 mClientMap.put(mListenerBinder, this);
97 }
98
99 void addProperty(int propId, float rate) {
100 mRateMap.put(propId, rate);
101 }
102
103 /**
104 * Client died. Remove the listener from HAL service and unregister if this is the last
105 * client.
106 */
107 @Override
108 public void binderDied() {
109 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800110 Slog.d(TAG, "binderDied " + mListenerBinder);
Steve Paik4d257022018-04-27 13:28:31 -0700111 }
112
113 for (int i = 0; i < mRateMap.size(); i++) {
114 int propId = mRateMap.keyAt(i);
115 CarPropertyService.this.unregisterListenerBinderLocked(propId, mListenerBinder);
116 }
117 this.release();
118 }
119
120 ICarPropertyEventListener getListener() {
121 return mListener;
122 }
123
124 IBinder getListenerBinder() {
125 return mListenerBinder;
126 }
127
128 float getRate(int propId) {
129 // Return 0 if no key found, since that is the slowest rate.
130 return mRateMap.get(propId, (float) 0);
131 }
132
133 void release() {
134 mListenerBinder.unlinkToDeath(this, 0);
135 mClientMap.remove(mListenerBinder);
136 }
137
138 void removeProperty(int propId) {
139 mRateMap.remove(propId);
140 if (mRateMap.size() == 0) {
141 // Last property was released, remove the client.
142 this.release();
143 }
144 }
145 }
146
147 @Override
148 public void init() {
Kai7a4e6ea2020-06-10 02:25:37 -0700149 synchronized (mLock) {
Kai579b5d32018-07-20 14:25:34 -0700150 // Cache the configs list to avoid subsequent binder calls
Kai7a4e6ea2020-06-10 02:25:37 -0700151 mConfigs.clear();
152 mConfigs.putAll(mHal.getPropertyList());
153 }
154 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800155 Slog.d(TAG, "cache CarPropertyConfigs " + mConfigs.size());
Kai579b5d32018-07-20 14:25:34 -0700156 }
Steve Paik4d257022018-04-27 13:28:31 -0700157 }
158
159 @Override
160 public void release() {
161 for (Client c : mClientMap.values()) {
162 c.release();
163 }
164 mClientMap.clear();
165 mPropIdClientMap.clear();
166 mHal.setListener(null);
167 mListenerIsSet = false;
Kai6e75d492020-02-20 16:11:40 -0800168 synchronized (mLock) {
169 mSetOperationClientMap.clear();
170 }
Steve Paik4d257022018-04-27 13:28:31 -0700171 }
172
173 @Override
Felipe Leme176a5fd2021-01-20 15:48:33 -0800174 public void dump(IndentingPrintWriter writer) {
Kai5a94d4b2020-05-13 22:16:41 -0700175 writer.println("*CarPropertyService*");
176 synchronized (mLock) {
177 writer.println(" Listener is set for PropertyHalService: " + mListenerIsSet);
178 writer.println(" There are " + mClientMap.size() + " clients "
179 + "using CarPropertyService.");
180 writer.println(" Properties registered: ");
181 for (int propId : mPropIdClientMap.keySet()) {
182 writer.println(" propId: 0x" + toHexString(propId)
183 + " is registered by " + mPropIdClientMap.get(propId).size()
184 + " client(s).");
185 }
186 writer.println(" Properties changed by CarPropertyService: ");
187 for (int i = 0; i < mSetOperationClientMap.size(); i++) {
188 int propId = mSetOperationClientMap.keyAt(i);
189 SparseArray areaIdToClient = mSetOperationClientMap.valueAt(i);
190 for (int j = 0; j < areaIdToClient.size(); j++) {
191 int areaId = areaIdToClient.keyAt(j);
192 writer.println(" propId: 0x" + toHexString(propId)
193 + " areaId: 0x" + toHexString(areaId)
194 + " by client: " + areaIdToClient.valueAt(j));
195 }
196 }
197 }
Steve Paik4d257022018-04-27 13:28:31 -0700198 }
199
200 @Override
201 public void registerListener(int propId, float rate, ICarPropertyEventListener listener) {
202 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800203 Slog.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate);
Steve Paik4d257022018-04-27 13:28:31 -0700204 }
Steve Paik4d257022018-04-27 13:28:31 -0700205 if (listener == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800206 Slog.e(TAG, "registerListener: Listener is null.");
Steve Paik4d257022018-04-27 13:28:31 -0700207 throw new IllegalArgumentException("listener cannot be null.");
208 }
209
210 IBinder listenerBinder = listener.asBinder();
Kaid5d8b922020-05-12 23:07:45 -0700211 CarPropertyConfig propertyConfig;
212 Client finalClient;
Steve Paik4d257022018-04-27 13:28:31 -0700213 synchronized (mLock) {
Kaid5d8b922020-05-12 23:07:45 -0700214 propertyConfig = mConfigs.get(propId);
215 if (propertyConfig == null) {
216 // Do not attempt to register an invalid propId
Eric Jeongbd5fb562020-12-21 13:49:40 -0800217 Slog.e(TAG, "registerListener: propId is not in config list: 0x" + toHexString(
Kaid5d8b922020-05-12 23:07:45 -0700218 propId));
219 return;
220 }
221 ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
222 // Get or create the client for this listener
Steve Paik4d257022018-04-27 13:28:31 -0700223 Client client = mClientMap.get(listenerBinder);
224 if (client == null) {
225 client = new Client(listener);
226 }
227 client.addProperty(propId, rate);
228 // Insert the client into the propId --> clients map
229 List<Client> clients = mPropIdClientMap.get(propId);
230 if (clients == null) {
231 clients = new CopyOnWriteArrayList<Client>();
232 mPropIdClientMap.put(propId, clients);
233 }
234 if (!clients.contains(client)) {
235 clients.add(client);
236 }
237 // Set the HAL listener if necessary
238 if (!mListenerIsSet) {
239 mHal.setListener(this);
240 }
241 // Set the new rate
242 if (rate > mHal.getSampleRate(propId)) {
243 mHal.subscribeProperty(propId, rate);
244 }
Kaid5d8b922020-05-12 23:07:45 -0700245 finalClient = client;
Steve Paik4d257022018-04-27 13:28:31 -0700246 }
Kaid5d8b922020-05-12 23:07:45 -0700247
248 // propertyConfig and client are NonNull.
249 mHandler.post(() ->
250 getAndDispatchPropertyInitValue(propertyConfig, finalClient));
251 }
252
253 private void getAndDispatchPropertyInitValue(CarPropertyConfig config, Client client) {
254 List<CarPropertyEvent> events = new LinkedList<>();
255 int propId = config.getPropertyId();
256 if (config.isGlobalProperty()) {
Kai79b6cfa2018-06-28 17:24:12 -0700257 CarPropertyValue value = mHal.getProperty(propId, 0);
Kai5831d732019-05-14 18:10:25 -0700258 if (value != null) {
259 CarPropertyEvent event = new CarPropertyEvent(
Kaid5d8b922020-05-12 23:07:45 -0700260 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
Kai5831d732019-05-14 18:10:25 -0700261 events.add(event);
262 }
Kai79b6cfa2018-06-28 17:24:12 -0700263 } else {
Kaid5d8b922020-05-12 23:07:45 -0700264 for (int areaId : config.getAreaIds()) {
Kai79b6cfa2018-06-28 17:24:12 -0700265 CarPropertyValue value = mHal.getProperty(propId, areaId);
Kai5831d732019-05-14 18:10:25 -0700266 if (value != null) {
267 CarPropertyEvent event = new CarPropertyEvent(
Kaid5d8b922020-05-12 23:07:45 -0700268 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
Kai5831d732019-05-14 18:10:25 -0700269 events.add(event);
270 }
Kai79b6cfa2018-06-28 17:24:12 -0700271 }
Steve Paik4d257022018-04-27 13:28:31 -0700272 }
273 try {
Kaid5d8b922020-05-12 23:07:45 -0700274 client.getListener().onEvent(events);
Steve Paik4d257022018-04-27 13:28:31 -0700275 } catch (RemoteException ex) {
276 // If we cannot send a record, its likely the connection snapped. Let the binder
277 // death handle the situation.
Eric Jeongbd5fb562020-12-21 13:49:40 -0800278 Slog.e(TAG, "onEvent calling failed: " + ex);
Steve Paik4d257022018-04-27 13:28:31 -0700279 }
280 }
281
282 @Override
283 public void unregisterListener(int propId, ICarPropertyEventListener listener) {
284 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800285 Slog.d(TAG, "unregisterListener propId=0x" + toHexString(propId));
Steve Paik4d257022018-04-27 13:28:31 -0700286 }
287 ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
288 if (listener == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800289 Slog.e(TAG, "unregisterListener: Listener is null.");
Steve Paik4d257022018-04-27 13:28:31 -0700290 throw new IllegalArgumentException("Listener is null");
291 }
292
293 IBinder listenerBinder = listener.asBinder();
294 synchronized (mLock) {
295 unregisterListenerBinderLocked(propId, listenerBinder);
296 }
297 }
298
299 private void unregisterListenerBinderLocked(int propId, IBinder listenerBinder) {
300 Client client = mClientMap.get(listenerBinder);
301 List<Client> propertyClients = mPropIdClientMap.get(propId);
Kai7a4e6ea2020-06-10 02:25:37 -0700302 synchronized (mLock) {
303 if (mConfigs.get(propId) == null) {
304 // Do not attempt to register an invalid propId
Eric Jeongbd5fb562020-12-21 13:49:40 -0800305 Slog.e(TAG, "unregisterListener: propId is not in config list:0x" + toHexString(
Kai7a4e6ea2020-06-10 02:25:37 -0700306 propId));
307 return;
308 }
Steve Paik4d257022018-04-27 13:28:31 -0700309 }
310 if ((client == null) || (propertyClients == null)) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800311 Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not previously registered.");
Steve Paik4d257022018-04-27 13:28:31 -0700312 } else {
313 if (propertyClients.remove(client)) {
314 client.removeProperty(propId);
Kai6e75d492020-02-20 16:11:40 -0800315 clearSetOperationRecorderLocked(propId, client);
Steve Paik4d257022018-04-27 13:28:31 -0700316 } else {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800317 Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for "
Steve Paik4d257022018-04-27 13:28:31 -0700318 + "propId=0x" + toHexString(propId));
319 }
320
321 if (propertyClients.isEmpty()) {
322 // Last listener for this property unsubscribed. Clean up
323 mHal.unsubscribeProperty(propId);
324 mPropIdClientMap.remove(propId);
Kai6e75d492020-02-20 16:11:40 -0800325 mSetOperationClientMap.remove(propId);
Steve Paik4d257022018-04-27 13:28:31 -0700326 if (mPropIdClientMap.isEmpty()) {
327 // No more properties are subscribed. Turn off the listener.
328 mHal.setListener(null);
329 mListenerIsSet = false;
330 }
331 } else {
332 // Other listeners are still subscribed. Calculate the new rate
333 float maxRate = 0;
334 for (Client c : propertyClients) {
335 float rate = c.getRate(propId);
336 if (rate > maxRate) {
337 maxRate = rate;
338 }
339 }
340 // Set the new rate
341 mHal.subscribeProperty(propId, maxRate);
342 }
343 }
344 }
345
346 /**
347 * Return the list of properties that the caller may access.
Steve Paik4d257022018-04-27 13:28:31 -0700348 */
349 @Override
350 public List<CarPropertyConfig> getPropertyList() {
351 List<CarPropertyConfig> returnList = new ArrayList<CarPropertyConfig>();
Kai7a4e6ea2020-06-10 02:25:37 -0700352 Set<CarPropertyConfig> allConfigs;
353 synchronized (mLock) {
354 allConfigs = new HashSet<>(mConfigs.values());
355 }
356 for (CarPropertyConfig c : allConfigs) {
Steve Paik4d257022018-04-27 13:28:31 -0700357 if (ICarImpl.hasPermission(mContext, mHal.getReadPermission(c.getPropertyId()))) {
358 // Only add properties the list if the process has permissions to read it
359 returnList.add(c);
360 }
361 }
362 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800363 Slog.d(TAG, "getPropertyList returns " + returnList.size() + " configs");
Steve Paik4d257022018-04-27 13:28:31 -0700364 }
365 return returnList;
366 }
367
368 @Override
369 public CarPropertyValue getProperty(int prop, int zone) {
Kai7a4e6ea2020-06-10 02:25:37 -0700370 synchronized (mLock) {
371 if (mConfigs.get(prop) == null) {
372 // Do not attempt to register an invalid propId
Eric Jeongbd5fb562020-12-21 13:49:40 -0800373 Slog.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop));
Kai7a4e6ea2020-06-10 02:25:37 -0700374 return null;
375 }
Steve Paik4d257022018-04-27 13:28:31 -0700376 }
377 ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop));
378 return mHal.getProperty(prop, zone);
379 }
380
381 @Override
Steve Paik03419e52018-10-29 17:25:45 -0700382 public String getReadPermission(int propId) {
Kai7a4e6ea2020-06-10 02:25:37 -0700383 synchronized (mLock) {
384 if (mConfigs.get(propId) == null) {
385 // Property ID does not exist
Eric Jeongbd5fb562020-12-21 13:49:40 -0800386 Slog.e(TAG,
Kai7a4e6ea2020-06-10 02:25:37 -0700387 "getReadPermission: propId is not in config list:0x" + toHexString(propId));
388 return null;
389 }
Steve Paik03419e52018-10-29 17:25:45 -0700390 }
391 return mHal.getReadPermission(propId);
392 }
393
394 @Override
395 public String getWritePermission(int propId) {
Kai7a4e6ea2020-06-10 02:25:37 -0700396 synchronized (mLock) {
397 if (mConfigs.get(propId) == null) {
398 // Property ID does not exist
Eric Jeongbd5fb562020-12-21 13:49:40 -0800399 Slog.e(TAG, "getWritePermission: propId is not in config list:0x" + toHexString(
Kai7a4e6ea2020-06-10 02:25:37 -0700400 propId));
401 return null;
402 }
Steve Paik03419e52018-10-29 17:25:45 -0700403 }
404 return mHal.getWritePermission(propId);
405 }
406
407 @Override
Kai6e75d492020-02-20 16:11:40 -0800408 public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) {
Steve Paik4d257022018-04-27 13:28:31 -0700409 int propId = prop.getPropertyId();
Kai3a8a5af2020-03-11 15:39:58 -0700410 checkPropertyAccessibility(propId);
Kaiafbb5022019-10-21 14:24:07 -0700411 // need an extra permission for writing display units properties.
412 if (mHal.isDisplayUnitsProperty(propId)) {
413 ICarImpl.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION);
414 }
Steve Paik4d257022018-04-27 13:28:31 -0700415 mHal.setProperty(prop);
Kai6e75d492020-02-20 16:11:40 -0800416 IBinder listenerBinder = listener.asBinder();
417 synchronized (mLock) {
418 Client client = mClientMap.get(listenerBinder);
419 if (client == null) {
420 client = new Client(listener);
421 }
422 updateSetOperationRecorder(propId, prop.getAreaId(), client);
423 }
424 }
425
Kai3a8a5af2020-03-11 15:39:58 -0700426 // The helper method checks if the vehicle has implemented this property and the property
427 // is accessible or not for platform and client.
428 private void checkPropertyAccessibility(int propId) {
429 // Checks if the car implemented the property or not.
Kai7a4e6ea2020-06-10 02:25:37 -0700430 synchronized (mLock) {
431 if (mConfigs.get(propId) == null) {
432 throw new IllegalArgumentException("Property Id: 0x" + Integer.toHexString(propId)
433 + " does not exist in the vehicle");
434 }
Kai3a8a5af2020-03-11 15:39:58 -0700435 }
436
437 // Checks if android has permission to write property.
438 String propertyWritePermission = mHal.getWritePermission(propId);
439 if (propertyWritePermission == null) {
440 throw new SecurityException("Platform does not have permission to change value for "
441 + "property Id: 0x" + Integer.toHexString(propId));
442 }
443 // Checks if the client has the permission.
444 ICarImpl.assertPermission(mContext, propertyWritePermission);
445 }
446
Kai6e75d492020-02-20 16:11:40 -0800447 // Updates recorder for set operation.
448 private void updateSetOperationRecorder(int propId, int areaId, Client client) {
449 if (mSetOperationClientMap.get(propId) != null) {
450 mSetOperationClientMap.get(propId).put(areaId, client);
451 } else {
452 SparseArray<Client> areaIdToClient = new SparseArray<>();
453 areaIdToClient.put(areaId, client);
454 mSetOperationClientMap.put(propId, areaIdToClient);
455 }
456 }
457
458 // Clears map when client unregister for property.
459 private void clearSetOperationRecorderLocked(int propId, Client client) {
460 SparseArray<Client> areaIdToClient = mSetOperationClientMap.get(propId);
461 if (areaIdToClient != null) {
462 List<Integer> indexNeedToRemove = new ArrayList<>();
463 for (int index = 0; index < areaIdToClient.size(); index++) {
464 if (client.equals(areaIdToClient.valueAt(index))) {
465 indexNeedToRemove.add(index);
466 }
467 }
468
469 for (int index : indexNeedToRemove) {
470 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800471 Slog.d("ErrorEvent", " Clear propId:0x" + toHexString(propId)
Kai6e75d492020-02-20 16:11:40 -0800472 + " areaId: 0x" + toHexString(areaIdToClient.keyAt(index)));
473 }
474 areaIdToClient.removeAt(index);
475 }
476 }
Steve Paik4d257022018-04-27 13:28:31 -0700477 }
478
479 // Implement PropertyHalListener interface
480 @Override
481 public void onPropertyChange(List<CarPropertyEvent> events) {
482 Map<IBinder, Pair<ICarPropertyEventListener, List<CarPropertyEvent>>> eventsToDispatch =
483 new HashMap<>();
484
485 for (CarPropertyEvent event : events) {
486 int propId = event.getCarPropertyValue().getPropertyId();
487 List<Client> clients = mPropIdClientMap.get(propId);
488 if (clients == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800489 Slog.e(TAG, "onPropertyChange: no listener registered for propId=0x"
Steve Paik4d257022018-04-27 13:28:31 -0700490 + toHexString(propId));
491 continue;
492 }
493
494 for (Client c : clients) {
495 IBinder listenerBinder = c.getListenerBinder();
496 Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p =
497 eventsToDispatch.get(listenerBinder);
498 if (p == null) {
499 // Initialize the linked list for the listener
500 p = new Pair<>(c.getListener(), new LinkedList<CarPropertyEvent>());
501 eventsToDispatch.put(listenerBinder, p);
502 }
503 p.second.add(event);
504 }
505 }
506 // Parse the dispatch list to send events
507 for (Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p: eventsToDispatch.values()) {
508 try {
509 p.first.onEvent(p.second);
510 } catch (RemoteException ex) {
511 // If we cannot send a record, its likely the connection snapped. Let binder
512 // death handle the situation.
Eric Jeongbd5fb562020-12-21 13:49:40 -0800513 Slog.e(TAG, "onEvent calling failed: " + ex);
Steve Paik4d257022018-04-27 13:28:31 -0700514 }
515 }
516 }
517
518 @Override
Kai6e75d492020-02-20 16:11:40 -0800519 public void onPropertySetError(int property, int areaId, int errorCode) {
520 Client lastOperatedClient = null;
521 synchronized (mLock) {
522 if (mSetOperationClientMap.get(property) != null
523 && mSetOperationClientMap.get(property).get(areaId) != null) {
524 lastOperatedClient = mSetOperationClientMap.get(property).get(areaId);
525 } else {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800526 Slog.e(TAG, "Can not find the client changed propertyId: 0x"
Kai6e75d492020-02-20 16:11:40 -0800527 + toHexString(property) + " in areaId: 0x" + toHexString(areaId));
Steve Paik4d257022018-04-27 13:28:31 -0700528 }
Kai6e75d492020-02-20 16:11:40 -0800529
530 }
531 if (lastOperatedClient != null) {
532 dispatchToLastClient(property, areaId, errorCode, lastOperatedClient);
533 }
534 }
535
536 private void dispatchToLastClient(int property, int areaId, int errorCode,
537 Client lastOperatedClient) {
538 try {
539 List<CarPropertyEvent> eventList = new LinkedList<>();
540 eventList.add(
541 CarPropertyEvent.createErrorEventWithErrorCode(property, areaId,
542 errorCode));
543 lastOperatedClient.getListener().onEvent(eventList);
544 } catch (RemoteException ex) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800545 Slog.e(TAG, "onEvent calling failed: " + ex);
Steve Paik4d257022018-04-27 13:28:31 -0700546 }
547 }
Steve Paik4d257022018-04-27 13:28:31 -0700548}