• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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