1 /* 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 17 package com.android.car; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN; 21 22 import static java.lang.Integer.toHexString; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.car.Car; 27 import android.car.builtin.util.Slogf; 28 import android.car.hardware.CarPropertyConfig; 29 import android.car.hardware.CarPropertyValue; 30 import android.car.hardware.property.CarPropertyEvent; 31 import android.car.hardware.property.ICarProperty; 32 import android.car.hardware.property.ICarPropertyEventListener; 33 import android.content.Context; 34 import android.os.Handler; 35 import android.os.HandlerThread; 36 import android.os.IBinder; 37 import android.os.RemoteException; 38 import android.os.ServiceSpecificException; 39 import android.util.ArrayMap; 40 import android.util.Pair; 41 import android.util.SparseArray; 42 43 import com.android.car.hal.PropertyHalService; 44 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 45 import com.android.car.internal.util.IndentingPrintWriter; 46 import com.android.internal.annotations.GuardedBy; 47 48 import java.util.ArrayList; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Set; 53 import java.util.concurrent.Callable; 54 55 /** 56 * This class implements the binder interface for ICarProperty.aidl to make it easier to create 57 * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in 58 * manager level. 59 */ 60 public class CarPropertyService extends ICarProperty.Stub 61 implements CarServiceBase, PropertyHalService.PropertyHalListener { 62 private static final boolean DBG = false; 63 private static final String TAG = CarLog.tagFor(CarPropertyService.class); 64 // Maximum count of sync get/set property operation allowed at once. The reason we limit this 65 // is because each sync get/set property operation takes up one binder thread. If they take 66 // all the binder thread, we do not have thread left for the result callback from VHAL. This 67 // will cause all the pending sync operation to timeout because result cannot be delivered. 68 private static final int SYNC_GET_SET_PROPERTY_OP_LIMIT = 16; 69 private final Context mContext; 70 private final PropertyHalService mHal; 71 private final Object mLock = new Object(); 72 @GuardedBy("mLock") 73 private final Map<IBinder, Client> mClientMap = new ArrayMap<>(); 74 @GuardedBy("mLock") 75 private final SparseArray<List<Client>> mPropIdClientMap = new SparseArray<>(); 76 @GuardedBy("mLock") 77 private final SparseArray<SparseArray<Client>> mSetOperationClientMap = new SparseArray<>(); 78 private final HandlerThread mHandlerThread = 79 CarServiceUtils.getHandlerThread(getClass().getSimpleName()); 80 private final Handler mHandler = new Handler(mHandlerThread.getLooper()); 81 // Use SparseArray instead of map to save memory. 82 @GuardedBy("mLock") 83 private SparseArray<CarPropertyConfig<?>> mConfigs = new SparseArray<>(); 84 @GuardedBy("mLock") 85 private SparseArray<Pair<String, String>> mPropToPermission = new SparseArray<>(); 86 @GuardedBy("mLock") 87 private int mSyncGetSetPropertyOpCount; 88 CarPropertyService(Context context, PropertyHalService hal)89 public CarPropertyService(Context context, PropertyHalService hal) { 90 if (DBG) { 91 Slogf.d(TAG, "CarPropertyService started!"); 92 } 93 mHal = hal; 94 mContext = context; 95 } 96 97 // Helper class to keep track of listeners to this service. 98 private class Client implements IBinder.DeathRecipient { 99 private final ICarPropertyEventListener mListener; 100 private final IBinder mListenerBinder; 101 private final Object mLock = new Object(); 102 // propId->rate map. 103 @GuardedBy("mLock") 104 private final SparseArray<Float> mRateMap = new SparseArray<Float>(); 105 @GuardedBy("mLock") 106 private boolean mIsDead = false; 107 Client(ICarPropertyEventListener listener)108 Client(ICarPropertyEventListener listener) { 109 mListener = listener; 110 mListenerBinder = listener.asBinder(); 111 112 try { 113 mListenerBinder.linkToDeath(this, 0); 114 } catch (RemoteException e) { 115 mIsDead = true; 116 } 117 } 118 119 /** 120 * Returns whether this client is already dead. 121 * 122 * Caller should not assume this client is alive after getting True response because the 123 * binder might die after this function checks the status. Caller should only use this 124 * function to fail early. 125 */ isDead()126 boolean isDead() { 127 synchronized (mLock) { 128 return mIsDead; 129 } 130 } 131 addProperty(int propId, float rate)132 void addProperty(int propId, float rate) { 133 synchronized (mLock) { 134 if (mIsDead) { 135 return; 136 } 137 mRateMap.put(propId, rate); 138 } 139 } 140 getRate(int propId)141 float getRate(int propId) { 142 synchronized (mLock) { 143 // Return 0 if no key found, since that is the slowest rate. 144 return mRateMap.get(propId, 0.0f); 145 } 146 } 147 removeProperty(int propId)148 int removeProperty(int propId) { 149 synchronized (mLock) { 150 mRateMap.remove(propId); 151 if (mRateMap.size() == 0) { 152 mListenerBinder.unlinkToDeath(this, 0); 153 } 154 return mRateMap.size(); 155 } 156 } 157 158 /** 159 * Handler to be called when client died. 160 * 161 * Remove the listener from HAL service and unregister if this is the last client. 162 */ 163 @Override binderDied()164 public void binderDied() { 165 List<Integer> propIds = new ArrayList<>(); 166 synchronized (mLock) { 167 mIsDead = true; 168 169 if (DBG) { 170 Slogf.d(TAG, "binderDied %s", mListenerBinder); 171 } 172 173 // Because we set mIsDead to true here, we are sure mRateMap would not have new 174 // elements. The propIds here is going to cover all the prop Ids that we need to 175 // unregister. 176 for (int i = 0; i < mRateMap.size(); i++) { 177 propIds.add(mRateMap.keyAt(i)); 178 } 179 } 180 181 CarPropertyService.this.unregisterListenerBinderForProps(propIds, mListenerBinder); 182 } 183 184 /** 185 * Calls onEvent function on the listener if the binder is alive. 186 * 187 * There is still chance when onEvent might fail because binderDied is not called before 188 * this function. 189 */ onEvent(List<CarPropertyEvent> events)190 void onEvent(List<CarPropertyEvent> events) throws RemoteException { 191 synchronized (mLock) { 192 if (mIsDead) { 193 return; 194 } 195 } 196 mListener.onEvent(events); 197 } 198 } 199 200 @Override init()201 public void init() { 202 synchronized (mLock) { 203 // Cache the configs list and permissions to avoid subsequent binder calls 204 mConfigs = mHal.getPropertyList(); 205 mPropToPermission = mHal.getPermissionsForAllProperties(); 206 if (DBG) { 207 Slogf.d(TAG, "cache CarPropertyConfigs " + mConfigs.size()); 208 } 209 } 210 mHal.setListener(this); 211 } 212 213 @Override release()214 public void release() { 215 synchronized (mLock) { 216 mClientMap.clear(); 217 mPropIdClientMap.clear(); 218 mHal.setListener(null); 219 mSetOperationClientMap.clear(); 220 } 221 } 222 223 @Override 224 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)225 public void dump(IndentingPrintWriter writer) { 226 writer.println("*CarPropertyService*"); 227 writer.increaseIndent(); 228 synchronized (mLock) { 229 writer.println(String.format("There are %d clients using CarPropertyService.", 230 mClientMap.size())); 231 writer.println("Properties registered: "); 232 writer.increaseIndent(); 233 for (int i = 0; i < mPropIdClientMap.size(); i++) { 234 int propId = mPropIdClientMap.keyAt(i); 235 writer.println("propId: 0x" + toHexString(propId) 236 + " is registered by " + mPropIdClientMap.valueAt(i).size() 237 + " client(s)."); 238 } 239 writer.decreaseIndent(); 240 writer.println("Properties changed by CarPropertyService: "); 241 writer.increaseIndent(); 242 for (int i = 0; i < mSetOperationClientMap.size(); i++) { 243 int propId = mSetOperationClientMap.keyAt(i); 244 SparseArray areaIdToClient = mSetOperationClientMap.valueAt(i); 245 for (int j = 0; j < areaIdToClient.size(); j++) { 246 int areaId = areaIdToClient.keyAt(j); 247 writer.println(String.format("propId: 0x%s areaId: 0x%s by client: %s", 248 toHexString(propId), toHexString(areaId), areaIdToClient.valueAt(j))); 249 } 250 } 251 writer.decreaseIndent(); 252 } 253 writer.decreaseIndent(); 254 } 255 256 @Override registerListener(int propId, float rate, ICarPropertyEventListener listener)257 public void registerListener(int propId, float rate, ICarPropertyEventListener listener) 258 throws IllegalArgumentException { 259 if (DBG) { 260 Slogf.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate); 261 } 262 if (listener == null) { 263 Slogf.e(TAG, "registerListener: Listener is null."); 264 throw new IllegalArgumentException("listener cannot be null."); 265 } 266 267 IBinder listenerBinder = listener.asBinder(); 268 CarPropertyConfig propertyConfig; 269 Client finalClient; 270 synchronized (mLock) { 271 propertyConfig = mConfigs.get(propId); 272 if (propertyConfig == null) { 273 // Do not attempt to register an invalid propId 274 Slogf.e(TAG, "registerListener: propId is not in config list: 0x" 275 + toHexString(propId)); 276 return; 277 } 278 CarServiceUtils.assertPermission(mContext, mHal.getReadPermission(propId)); 279 // Get or create the client for this listener 280 Client client = mClientMap.get(listenerBinder); 281 if (client == null) { 282 client = new Client(listener); 283 if (client.isDead()) { 284 Slogf.w(TAG, "the ICarPropertyEventListener is already dead"); 285 return; 286 } 287 mClientMap.put(listenerBinder, client); 288 } 289 client.addProperty(propId, rate); 290 // Insert the client into the propId --> clients map 291 List<Client> clients = mPropIdClientMap.get(propId); 292 if (clients == null) { 293 clients = new ArrayList<Client>(); 294 mPropIdClientMap.put(propId, clients); 295 } 296 if (!clients.contains(client)) { 297 clients.add(client); 298 } 299 // Set the new rate 300 if (rate > mHal.getSampleRate(propId)) { 301 mHal.subscribeProperty(propId, rate); 302 } 303 finalClient = client; 304 } 305 306 // propertyConfig and client are NonNull. 307 mHandler.post(() -> 308 getAndDispatchPropertyInitValue(propertyConfig, finalClient)); 309 } 310 getAndDispatchPropertyInitValue(CarPropertyConfig config, Client client)311 private void getAndDispatchPropertyInitValue(CarPropertyConfig config, Client client) { 312 List<CarPropertyEvent> events = new ArrayList<>(); 313 int propId = config.getPropertyId(); 314 if (config.isGlobalProperty()) { 315 CarPropertyValue value = mHal.getPropertySafe(propId, 0); 316 if (value != null) { 317 CarPropertyEvent event = new CarPropertyEvent( 318 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); 319 events.add(event); 320 } 321 } else { 322 for (int areaId : config.getAreaIds()) { 323 CarPropertyValue value = mHal.getPropertySafe(propId, areaId); 324 if (value != null) { 325 CarPropertyEvent event = new CarPropertyEvent( 326 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); 327 events.add(event); 328 } 329 } 330 } 331 if (events.isEmpty()) { 332 return; 333 } 334 try { 335 client.onEvent(events); 336 } catch (RemoteException ex) { 337 // If we cannot send a record, its likely the connection snapped. Let the binder 338 // death handle the situation. 339 Slogf.e(TAG, "onEvent calling failed", ex); 340 } 341 } 342 343 @Override unregisterListener(int propId, ICarPropertyEventListener listener)344 public void unregisterListener(int propId, ICarPropertyEventListener listener) { 345 if (DBG) { 346 Slogf.d(TAG, "unregisterListener propId=0x" + toHexString(propId)); 347 } 348 CarServiceUtils.assertPermission(mContext, mHal.getReadPermission(propId)); 349 if (listener == null) { 350 Slogf.e(TAG, "unregisterListener: Listener is null."); 351 throw new IllegalArgumentException("Listener is null"); 352 } 353 354 IBinder listenerBinder = listener.asBinder(); 355 unregisterListenerBinderForProps(List.of(propId), listenerBinder); 356 } 357 358 @GuardedBy("mLock") unregisterListenerBinderLocked(int propId, IBinder listenerBinder)359 private void unregisterListenerBinderLocked(int propId, IBinder listenerBinder) { 360 float updateMaxRate = 0f; 361 Client client = mClientMap.get(listenerBinder); 362 List<Client> propertyClients = mPropIdClientMap.get(propId); 363 if (mConfigs.get(propId) == null) { 364 // Do not attempt to unregister an invalid propId 365 Slogf.e(TAG, "unregisterListener: propId is not in config list:0x%s", 366 toHexString(propId)); 367 return; 368 } 369 if ((client == null) || (propertyClients == null)) { 370 Slogf.e(TAG, "unregisterListenerBinderLocked: Listener was not previously " 371 + "registered."); 372 return; 373 } 374 if (propertyClients.remove(client)) { 375 int propLeft = client.removeProperty(propId); 376 if (propLeft == 0) { 377 mClientMap.remove(listenerBinder); 378 } 379 clearSetOperationRecorderLocked(propId, client); 380 381 } else { 382 Slogf.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for " 383 + "propId=0x" + toHexString(propId)); 384 return; 385 } 386 387 if (propertyClients.isEmpty()) { 388 // Last listener for this property unsubscribed. Clean up 389 mPropIdClientMap.remove(propId); 390 mSetOperationClientMap.remove(propId); 391 mHal.unsubscribeProperty(propId); 392 return; 393 } 394 // Other listeners are still subscribed. Calculate the new rate 395 for (int i = 0; i < propertyClients.size(); i++) { 396 Client c = propertyClients.get(i); 397 float rate = c.getRate(propId); 398 updateMaxRate = Math.max(rate, updateMaxRate); 399 } 400 if (Float.compare(updateMaxRate, mHal.getSampleRate(propId)) != 0) { 401 try { 402 // Only reset the sample rate if needed 403 mHal.subscribeProperty(propId, updateMaxRate); 404 } catch (IllegalArgumentException e) { 405 Slogf.e(TAG, "failed to subscribe to propId=0x" + toHexString(propId) 406 + ", error: " + e); 407 } 408 } 409 } 410 unregisterListenerBinderForProps(List<Integer> propIds, IBinder listenerBinder)411 private void unregisterListenerBinderForProps(List<Integer> propIds, IBinder listenerBinder) { 412 synchronized (mLock) { 413 for (int i = 0; i < propIds.size(); i++) { 414 int propId = propIds.get(i); 415 unregisterListenerBinderLocked(propId, listenerBinder); 416 } 417 } 418 } 419 420 /** 421 * Return the list of properties' configs that the caller may access. 422 */ 423 @NonNull 424 @Override getPropertyList()425 public List<CarPropertyConfig> getPropertyList() { 426 int[] allPropId; 427 // Avoid permission checking under lock. 428 synchronized (mLock) { 429 allPropId = new int[mConfigs.size()]; 430 for (int i = 0; i < mConfigs.size(); i++) { 431 allPropId[i] = mConfigs.keyAt(i); 432 } 433 } 434 return getPropertyConfigList(allPropId); 435 } 436 437 /** 438 * 439 * @param propIds Array of property Ids 440 * @return the list of properties' configs that the caller may access. 441 */ 442 @NonNull 443 @Override getPropertyConfigList(int[] propIds)444 public List<CarPropertyConfig> getPropertyConfigList(int[] propIds) { 445 // Cache the granted permissions 446 Set<String> grantedPermission = new HashSet<>(); 447 List<CarPropertyConfig> availableProp = new ArrayList<>(); 448 if (propIds == null) { 449 return availableProp; 450 } 451 for (int propId : propIds) { 452 String readPermission = getReadPermission(propId); 453 String writePermission = getWritePermission(propId); 454 if (readPermission == null && writePermission == null) { 455 continue; 456 } 457 // Check if context already granted permission first 458 if (checkAndUpdateGrantedPermissionSet(mContext, grantedPermission, readPermission) 459 || checkAndUpdateGrantedPermissionSet(mContext, grantedPermission, 460 writePermission)) { 461 synchronized (mLock) { 462 availableProp.add(mConfigs.get(propId)); 463 } 464 } 465 } 466 if (DBG) { 467 Slogf.d(TAG, "getPropertyList returns " + availableProp.size() + " configs"); 468 } 469 return availableProp; 470 } 471 checkAndUpdateGrantedPermissionSet(Context context, Set<String> grantedPermissions, @Nullable String permission)472 private static boolean checkAndUpdateGrantedPermissionSet(Context context, 473 Set<String> grantedPermissions, @Nullable String permission) { 474 if (permission != null && (grantedPermissions.contains(permission) 475 || CarServiceUtils.hasPermission(context, permission))) { 476 grantedPermissions.add(permission); 477 return true; 478 } 479 return false; 480 } 481 runSyncOperationCheckLimit(Callable<V> c)482 private <V> V runSyncOperationCheckLimit(Callable<V> c) { 483 synchronized (mLock) { 484 if (mSyncGetSetPropertyOpCount >= SYNC_GET_SET_PROPERTY_OP_LIMIT) { 485 throw new ServiceSpecificException(SYNC_OP_LIMIT_TRY_AGAIN); 486 } 487 mSyncGetSetPropertyOpCount += 1; 488 Slogf.d(TAG, "mSyncGetSetPropertyOpCount: " + mSyncGetSetPropertyOpCount); 489 } 490 try { 491 return c.call(); 492 } catch (RuntimeException e) { 493 throw e; 494 } catch (Exception e) { 495 Slogf.e(TAG, e, "catching unexpected exception for getProperty/setProperty"); 496 return null; 497 } finally { 498 synchronized (mLock) { 499 mSyncGetSetPropertyOpCount -= 1; 500 Slogf.d(TAG, "mSyncGetSetPropertyOpCount: " + mSyncGetSetPropertyOpCount); 501 } 502 } 503 } 504 505 @Override getProperty(int prop, int zone)506 public CarPropertyValue getProperty(int prop, int zone) 507 throws IllegalArgumentException, ServiceSpecificException { 508 synchronized (mLock) { 509 if (mConfigs.get(prop) == null) { 510 // Do not attempt to register an invalid propId 511 Slogf.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop)); 512 return null; 513 } 514 } 515 // Checks if android has permission to read property. 516 String permission = mHal.getReadPermission(prop); 517 if (permission == null) { 518 throw new SecurityException("Platform does not have permission to read value for " 519 + "property Id: 0x" + Integer.toHexString(prop)); 520 } 521 CarServiceUtils.assertPermission(mContext, permission); 522 return runSyncOperationCheckLimit(() -> { 523 return mHal.getProperty(prop, zone); 524 }); 525 } 526 527 /** 528 * Get property value for car service's internal usage. 529 * @param prop property id 530 * @param zone area id 531 * @return null if property is not implemented or there is an exception in the vehicle. 532 */ getPropertySafe(int prop, int zone)533 public CarPropertyValue getPropertySafe(int prop, int zone) { 534 synchronized (mLock) { 535 if (mConfigs.get(prop) == null) { 536 // Do not attempt to register an invalid propId 537 Slogf.e(TAG, "getPropertySafe: propId is not in config list:0x" 538 + toHexString(prop)); 539 return null; 540 } 541 } 542 CarServiceUtils.assertPermission(mContext, mHal.getReadPermission(prop)); 543 return mHal.getPropertySafe(prop, zone); 544 } 545 546 @Nullable 547 @Override getReadPermission(int propId)548 public String getReadPermission(int propId) { 549 Pair<String, String> permissions; 550 synchronized (mLock) { 551 permissions = mPropToPermission.get(propId); 552 } 553 if (permissions == null) { 554 // Property ID does not exist 555 Slogf.e(TAG, "getReadPermission: propId is not in config list:0x" 556 + toHexString(propId)); 557 return null; 558 } 559 return permissions.first; 560 } 561 562 @Nullable 563 @Override getWritePermission(int propId)564 public String getWritePermission(int propId) { 565 Pair<String, String> permissions; 566 synchronized (mLock) { 567 permissions = mPropToPermission.get(propId); 568 } 569 if (permissions == null) { 570 // Property ID does not exist 571 Slogf.e(TAG, "getWritePermission: propId is not in config list:0x" 572 + toHexString(propId)); 573 return null; 574 } 575 return permissions.second; 576 } 577 578 @Override setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)579 public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) 580 throws IllegalArgumentException, ServiceSpecificException { 581 int propId = prop.getPropertyId(); 582 checkPropertyAccessibility(propId); 583 // need an extra permission for writing display units properties. 584 if (mHal.isDisplayUnitsProperty(propId)) { 585 CarServiceUtils.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION); 586 } 587 runSyncOperationCheckLimit(() -> { 588 mHal.setProperty(prop); 589 return null; 590 }); 591 IBinder listenerBinder = listener.asBinder(); 592 synchronized (mLock) { 593 Client client = mClientMap.get(listenerBinder); 594 if (client == null) { 595 client = new Client(listener); 596 } 597 if (client.isDead()) { 598 Slogf.w(TAG, "the ICarPropertyEventListener is already dead"); 599 return; 600 } 601 mClientMap.put(listenerBinder, client); 602 updateSetOperationRecorderLocked(propId, prop.getAreaId(), client); 603 } 604 } 605 606 // The helper method checks if the vehicle has implemented this property and the property 607 // is accessible or not for platform and client. checkPropertyAccessibility(int propId)608 private void checkPropertyAccessibility(int propId) { 609 // Checks if the car implemented the property or not. 610 synchronized (mLock) { 611 if (mConfigs.get(propId) == null) { 612 throw new IllegalArgumentException("Property Id: 0x" + Integer.toHexString(propId) 613 + " does not exist in the vehicle"); 614 } 615 } 616 617 // Checks if android has permission to write property. 618 String propertyWritePermission = mHal.getWritePermission(propId); 619 if (propertyWritePermission == null) { 620 throw new SecurityException("Platform does not have permission to change value for " 621 + "property Id: 0x" + Integer.toHexString(propId)); 622 } 623 // Checks if the client has the permission. 624 CarServiceUtils.assertPermission(mContext, propertyWritePermission); 625 } 626 627 // Updates recorder for set operation. 628 @GuardedBy("mLock") updateSetOperationRecorderLocked(int propId, int areaId, Client client)629 private void updateSetOperationRecorderLocked(int propId, int areaId, Client client) { 630 if (mSetOperationClientMap.get(propId) != null) { 631 mSetOperationClientMap.get(propId).put(areaId, client); 632 } else { 633 SparseArray<Client> areaIdToClient = new SparseArray<>(); 634 areaIdToClient.put(areaId, client); 635 mSetOperationClientMap.put(propId, areaIdToClient); 636 } 637 } 638 639 // Clears map when client unregister for property. 640 @GuardedBy("mLock") clearSetOperationRecorderLocked(int propId, Client client)641 private void clearSetOperationRecorderLocked(int propId, Client client) { 642 SparseArray<Client> areaIdToClient = mSetOperationClientMap.get(propId); 643 if (areaIdToClient != null) { 644 List<Integer> indexNeedToRemove = new ArrayList<>(); 645 for (int index = 0; index < areaIdToClient.size(); index++) { 646 if (client.equals(areaIdToClient.valueAt(index))) { 647 indexNeedToRemove.add(index); 648 } 649 } 650 651 for (int index : indexNeedToRemove) { 652 if (DBG) { 653 Slogf.d("ErrorEvent", " Clear propId:0x" + toHexString(propId) 654 + " areaId: 0x" + toHexString(areaIdToClient.keyAt(index))); 655 } 656 areaIdToClient.removeAt(index); 657 } 658 } 659 } 660 661 // Implement PropertyHalListener interface 662 @Override onPropertyChange(List<CarPropertyEvent> events)663 public void onPropertyChange(List<CarPropertyEvent> events) { 664 Map<Client, List<CarPropertyEvent>> eventsToDispatch = new ArrayMap<>(); 665 synchronized (mLock) { 666 for (int i = 0; i < events.size(); i++) { 667 CarPropertyEvent event = events.get(i); 668 int propId = event.getCarPropertyValue().getPropertyId(); 669 List<Client> clients = mPropIdClientMap.get(propId); 670 if (clients == null) { 671 Slogf.e(TAG, "onPropertyChange: no listener registered for propId=0x%s", 672 toHexString(propId)); 673 continue; 674 } 675 676 for (int j = 0; j < clients.size(); j++) { 677 Client c = clients.get(j); 678 List<CarPropertyEvent> p = eventsToDispatch.get(c); 679 if (p == null) { 680 // Initialize the linked list for the listener 681 p = new ArrayList<CarPropertyEvent>(); 682 eventsToDispatch.put(c, p); 683 } 684 p.add(event); 685 } 686 } 687 } 688 689 // Parse the dispatch list to send events. We must call the callback outside the 690 // scoped lock since the callback might call some function in CarPropertyService 691 // which might cause dead-lock. 692 // In rare cases, if this specific client is unregistered after the lock but before 693 // the callback, we would call callback on an unregistered client which should be ok because 694 // 'onEvent' is an async oneway callback that might be delivered after unregistration 695 // anyway. 696 for (Client client : eventsToDispatch.keySet()) { 697 try { 698 client.onEvent(eventsToDispatch.get(client)); 699 } catch (RemoteException ex) { 700 // If we cannot send a record, its likely the connection snapped. Let binder 701 // death handle the situation. 702 Slogf.e(TAG, "onEvent calling failed: " + ex); 703 } 704 } 705 } 706 707 @Override onPropertySetError(int property, int areaId, int errorCode)708 public void onPropertySetError(int property, int areaId, int errorCode) { 709 Client lastOperatedClient = null; 710 synchronized (mLock) { 711 if (mSetOperationClientMap.get(property) != null 712 && mSetOperationClientMap.get(property).get(areaId) != null) { 713 lastOperatedClient = mSetOperationClientMap.get(property).get(areaId); 714 } else { 715 Slogf.e(TAG, "Can not find the client changed propertyId: 0x" 716 + toHexString(property) + " in areaId: 0x" + toHexString(areaId)); 717 } 718 719 } 720 if (lastOperatedClient != null) { 721 dispatchToLastClient(property, areaId, errorCode, lastOperatedClient); 722 } 723 } 724 dispatchToLastClient(int property, int areaId, int errorCode, Client lastOperatedClient)725 private void dispatchToLastClient(int property, int areaId, int errorCode, 726 Client lastOperatedClient) { 727 try { 728 List<CarPropertyEvent> eventList = new ArrayList<>(); 729 eventList.add( 730 CarPropertyEvent.createErrorEventWithErrorCode(property, areaId, 731 errorCode)); 732 lastOperatedClient.onEvent(eventList); 733 } catch (RemoteException ex) { 734 Slogf.e(TAG, "onEvent calling failed: " + ex); 735 } 736 } 737 } 738