blob: e8f4aab424ffc79ddc4c10a0d5cf04020c041d59 [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
Kaibaeacf22020-11-15 19:55:10 -080021import android.annotation.NonNull;
22import android.annotation.Nullable;
Kaiafbb5022019-10-21 14:24:07 -070023import android.car.Car;
Steve Paik4d257022018-04-27 13:28:31 -070024import android.car.hardware.CarPropertyConfig;
25import android.car.hardware.CarPropertyValue;
26import android.car.hardware.property.CarPropertyEvent;
27import android.car.hardware.property.ICarProperty;
28import android.car.hardware.property.ICarPropertyEventListener;
29import android.content.Context;
Kaid5d8b922020-05-12 23:07:45 -070030import android.os.Handler;
31import android.os.HandlerThread;
Steve Paik4d257022018-04-27 13:28:31 -070032import android.os.IBinder;
33import android.os.RemoteException;
Felipe Leme176a5fd2021-01-20 15:48:33 -080034import android.util.IndentingPrintWriter;
Steve Paik4d257022018-04-27 13:28:31 -070035import android.util.Pair;
Eric Jeongbd5fb562020-12-21 13:49:40 -080036import android.util.Slog;
Steve Paik4d257022018-04-27 13:28:31 -070037import android.util.SparseArray;
38
39import com.android.car.hal.PropertyHalService;
Kai6e75d492020-02-20 16:11:40 -080040import com.android.internal.annotations.GuardedBy;
Steve Paik4d257022018-04-27 13:28:31 -070041
Steve Paik4d257022018-04-27 13:28:31 -070042import java.util.ArrayList;
43import java.util.HashMap;
Kai7a4e6ea2020-06-10 02:25:37 -070044import java.util.HashSet;
Steve Paik4d257022018-04-27 13:28:31 -070045import java.util.LinkedList;
46import java.util.List;
47import java.util.Map;
Kai7a4e6ea2020-06-10 02:25:37 -070048import java.util.Set;
Steve Paik4d257022018-04-27 13:28:31 -070049import java.util.concurrent.ConcurrentHashMap;
50import java.util.concurrent.CopyOnWriteArrayList;
51
52/**
53 * This class implements the binder interface for ICarProperty.aidl to make it easier to create
Kai3a8a5af2020-03-11 15:39:58 -070054 * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in
55 * manager level.
Steve Paik4d257022018-04-27 13:28:31 -070056 */
57public class CarPropertyService extends ICarProperty.Stub
58 implements CarServiceBase, PropertyHalService.PropertyHalListener {
59 private static final boolean DBG = true;
Mayank Garg72c71d22021-02-03 23:54:45 -080060 private static final String TAG = CarLog.tagFor(CarPropertyService.class);
Steve Paik4d257022018-04-27 13:28:31 -070061 private final Context mContext;
62 private final Map<IBinder, Client> mClientMap = new ConcurrentHashMap<>();
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());
Kaibaeacf22020-11-15 19:55:10 -080072 // Use SparseArray instead of map to save memory.
73 @GuardedBy("mLock")
74 private SparseArray<CarPropertyConfig<?>> mConfigs = new SparseArray<>();
75 @GuardedBy("mLock")
76 private SparseArray<Pair<String, String>> mPropToPermission = new SparseArray<>();
Kaid5d8b922020-05-12 23:07:45 -070077
Steve Paik4d257022018-04-27 13:28:31 -070078 public CarPropertyService(Context context, PropertyHalService hal) {
79 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -080080 Slog.d(TAG, "CarPropertyService started!");
Steve Paik4d257022018-04-27 13:28:31 -070081 }
82 mHal = hal;
83 mContext = context;
84 }
85
86 // Helper class to keep track of listeners to this service
87 private class Client implements IBinder.DeathRecipient {
88 private final ICarPropertyEventListener mListener;
89 private final IBinder mListenerBinder;
90 private final SparseArray<Float> mRateMap = new SparseArray<Float>(); // key is propId
91
92 Client(ICarPropertyEventListener listener) {
93 mListener = listener;
94 mListenerBinder = listener.asBinder();
95
96 try {
97 mListenerBinder.linkToDeath(this, 0);
98 } catch (RemoteException e) {
Justin Paupore0dd31772019-02-07 21:22:34 -080099 throw new IllegalStateException("Client already dead", e);
Steve Paik4d257022018-04-27 13:28:31 -0700100 }
101 mClientMap.put(mListenerBinder, this);
102 }
103
104 void addProperty(int propId, float rate) {
105 mRateMap.put(propId, rate);
106 }
107
108 /**
109 * Client died. Remove the listener from HAL service and unregister if this is the last
110 * client.
111 */
112 @Override
113 public void binderDied() {
114 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800115 Slog.d(TAG, "binderDied " + mListenerBinder);
Steve Paik4d257022018-04-27 13:28:31 -0700116 }
117
118 for (int i = 0; i < mRateMap.size(); i++) {
119 int propId = mRateMap.keyAt(i);
120 CarPropertyService.this.unregisterListenerBinderLocked(propId, mListenerBinder);
121 }
122 this.release();
123 }
124
125 ICarPropertyEventListener getListener() {
126 return mListener;
127 }
128
129 IBinder getListenerBinder() {
130 return mListenerBinder;
131 }
132
133 float getRate(int propId) {
134 // Return 0 if no key found, since that is the slowest rate.
135 return mRateMap.get(propId, (float) 0);
136 }
137
138 void release() {
139 mListenerBinder.unlinkToDeath(this, 0);
140 mClientMap.remove(mListenerBinder);
141 }
142
143 void removeProperty(int propId) {
144 mRateMap.remove(propId);
145 if (mRateMap.size() == 0) {
146 // Last property was released, remove the client.
147 this.release();
148 }
149 }
150 }
151
152 @Override
153 public void init() {
Kai7a4e6ea2020-06-10 02:25:37 -0700154 synchronized (mLock) {
Kaibaeacf22020-11-15 19:55:10 -0800155 // Cache the configs list and permissions to avoid subsequent binder calls
156 mConfigs = mHal.getPropertyList();
157 mPropToPermission = mHal.getPermissionsForAllProperties();
Kai7a4e6ea2020-06-10 02:25:37 -0700158 }
159 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800160 Slog.d(TAG, "cache CarPropertyConfigs " + mConfigs.size());
Kai579b5d32018-07-20 14:25:34 -0700161 }
Steve Paik4d257022018-04-27 13:28:31 -0700162 }
163
164 @Override
165 public void release() {
166 for (Client c : mClientMap.values()) {
167 c.release();
168 }
169 mClientMap.clear();
170 mPropIdClientMap.clear();
171 mHal.setListener(null);
172 mListenerIsSet = false;
Kai6e75d492020-02-20 16:11:40 -0800173 synchronized (mLock) {
174 mSetOperationClientMap.clear();
175 }
Steve Paik4d257022018-04-27 13:28:31 -0700176 }
177
178 @Override
Felipe Leme176a5fd2021-01-20 15:48:33 -0800179 public void dump(IndentingPrintWriter writer) {
Kai5a94d4b2020-05-13 22:16:41 -0700180 writer.println("*CarPropertyService*");
Kai29043b12021-02-10 20:38:57 -0800181 writer.increaseIndent();
Kai5a94d4b2020-05-13 22:16:41 -0700182 synchronized (mLock) {
Kai29043b12021-02-10 20:38:57 -0800183 writer.println(String.format("Listener is set for PropertyHalService: %s",
184 mListenerIsSet));
185 writer.println(String.format("There are %d clients using CarPropertyService.",
186 mClientMap.size()));
187 writer.println("Properties registered: ");
188 writer.increaseIndent();
Kai5a94d4b2020-05-13 22:16:41 -0700189 for (int propId : mPropIdClientMap.keySet()) {
Kai29043b12021-02-10 20:38:57 -0800190 writer.println("propId: 0x" + toHexString(propId)
Kai5a94d4b2020-05-13 22:16:41 -0700191 + " is registered by " + mPropIdClientMap.get(propId).size()
192 + " client(s).");
193 }
Kai29043b12021-02-10 20:38:57 -0800194 writer.decreaseIndent();
195 writer.println("Properties changed by CarPropertyService: ");
196 writer.increaseIndent();
Kai5a94d4b2020-05-13 22:16:41 -0700197 for (int i = 0; i < mSetOperationClientMap.size(); i++) {
198 int propId = mSetOperationClientMap.keyAt(i);
199 SparseArray areaIdToClient = mSetOperationClientMap.valueAt(i);
200 for (int j = 0; j < areaIdToClient.size(); j++) {
201 int areaId = areaIdToClient.keyAt(j);
Kai29043b12021-02-10 20:38:57 -0800202 writer.println(String.format("propId: 0x%s areaId: 0x%s by client: %s",
203 toHexString(propId), toHexString(areaId), areaIdToClient.valueAt(j)));
Kai5a94d4b2020-05-13 22:16:41 -0700204 }
205 }
Kai29043b12021-02-10 20:38:57 -0800206 writer.decreaseIndent();
Kai5a94d4b2020-05-13 22:16:41 -0700207 }
Kai29043b12021-02-10 20:38:57 -0800208 writer.decreaseIndent();
Steve Paik4d257022018-04-27 13:28:31 -0700209 }
210
211 @Override
212 public void registerListener(int propId, float rate, ICarPropertyEventListener listener) {
213 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800214 Slog.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate);
Steve Paik4d257022018-04-27 13:28:31 -0700215 }
Steve Paik4d257022018-04-27 13:28:31 -0700216 if (listener == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800217 Slog.e(TAG, "registerListener: Listener is null.");
Steve Paik4d257022018-04-27 13:28:31 -0700218 throw new IllegalArgumentException("listener cannot be null.");
219 }
220
221 IBinder listenerBinder = listener.asBinder();
Kaid5d8b922020-05-12 23:07:45 -0700222 CarPropertyConfig propertyConfig;
223 Client finalClient;
Steve Paik4d257022018-04-27 13:28:31 -0700224 synchronized (mLock) {
Kaid5d8b922020-05-12 23:07:45 -0700225 propertyConfig = mConfigs.get(propId);
226 if (propertyConfig == null) {
227 // Do not attempt to register an invalid propId
Eric Jeongbd5fb562020-12-21 13:49:40 -0800228 Slog.e(TAG, "registerListener: propId is not in config list: 0x" + toHexString(
Kaid5d8b922020-05-12 23:07:45 -0700229 propId));
230 return;
231 }
232 ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
233 // Get or create the client for this listener
Steve Paik4d257022018-04-27 13:28:31 -0700234 Client client = mClientMap.get(listenerBinder);
235 if (client == null) {
236 client = new Client(listener);
237 }
238 client.addProperty(propId, rate);
239 // Insert the client into the propId --> clients map
240 List<Client> clients = mPropIdClientMap.get(propId);
241 if (clients == null) {
242 clients = new CopyOnWriteArrayList<Client>();
243 mPropIdClientMap.put(propId, clients);
244 }
245 if (!clients.contains(client)) {
246 clients.add(client);
247 }
248 // Set the HAL listener if necessary
249 if (!mListenerIsSet) {
250 mHal.setListener(this);
251 }
252 // Set the new rate
253 if (rate > mHal.getSampleRate(propId)) {
254 mHal.subscribeProperty(propId, rate);
255 }
Kaid5d8b922020-05-12 23:07:45 -0700256 finalClient = client;
Steve Paik4d257022018-04-27 13:28:31 -0700257 }
Kaid5d8b922020-05-12 23:07:45 -0700258
259 // propertyConfig and client are NonNull.
260 mHandler.post(() ->
261 getAndDispatchPropertyInitValue(propertyConfig, finalClient));
262 }
263
264 private void getAndDispatchPropertyInitValue(CarPropertyConfig config, Client client) {
265 List<CarPropertyEvent> events = new LinkedList<>();
266 int propId = config.getPropertyId();
267 if (config.isGlobalProperty()) {
Kai Wang301dd472021-05-18 06:09:44 +0000268 CarPropertyValue value = mHal.getPropertySafe(propId, 0);
Kai5831d732019-05-14 18:10:25 -0700269 if (value != null) {
270 CarPropertyEvent event = new CarPropertyEvent(
Kaid5d8b922020-05-12 23:07:45 -0700271 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
Kai5831d732019-05-14 18:10:25 -0700272 events.add(event);
273 }
Kai79b6cfa2018-06-28 17:24:12 -0700274 } else {
Kaid5d8b922020-05-12 23:07:45 -0700275 for (int areaId : config.getAreaIds()) {
Kai Wang301dd472021-05-18 06:09:44 +0000276 CarPropertyValue value = mHal.getPropertySafe(propId, areaId);
Kai5831d732019-05-14 18:10:25 -0700277 if (value != null) {
278 CarPropertyEvent event = new CarPropertyEvent(
Kaid5d8b922020-05-12 23:07:45 -0700279 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
Kai5831d732019-05-14 18:10:25 -0700280 events.add(event);
281 }
Kai79b6cfa2018-06-28 17:24:12 -0700282 }
Steve Paik4d257022018-04-27 13:28:31 -0700283 }
284 try {
Kaid5d8b922020-05-12 23:07:45 -0700285 client.getListener().onEvent(events);
Steve Paik4d257022018-04-27 13:28:31 -0700286 } catch (RemoteException ex) {
287 // If we cannot send a record, its likely the connection snapped. Let the binder
288 // death handle the situation.
Eric Jeongbd5fb562020-12-21 13:49:40 -0800289 Slog.e(TAG, "onEvent calling failed: " + ex);
Steve Paik4d257022018-04-27 13:28:31 -0700290 }
291 }
292
293 @Override
294 public void unregisterListener(int propId, ICarPropertyEventListener listener) {
295 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800296 Slog.d(TAG, "unregisterListener propId=0x" + toHexString(propId));
Steve Paik4d257022018-04-27 13:28:31 -0700297 }
298 ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
299 if (listener == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800300 Slog.e(TAG, "unregisterListener: Listener is null.");
Steve Paik4d257022018-04-27 13:28:31 -0700301 throw new IllegalArgumentException("Listener is null");
302 }
303
304 IBinder listenerBinder = listener.asBinder();
305 synchronized (mLock) {
306 unregisterListenerBinderLocked(propId, listenerBinder);
307 }
308 }
309
310 private void unregisterListenerBinderLocked(int propId, IBinder listenerBinder) {
311 Client client = mClientMap.get(listenerBinder);
312 List<Client> propertyClients = mPropIdClientMap.get(propId);
Kai7a4e6ea2020-06-10 02:25:37 -0700313 synchronized (mLock) {
314 if (mConfigs.get(propId) == null) {
315 // Do not attempt to register an invalid propId
Eric Jeongbd5fb562020-12-21 13:49:40 -0800316 Slog.e(TAG, "unregisterListener: propId is not in config list:0x" + toHexString(
Kai7a4e6ea2020-06-10 02:25:37 -0700317 propId));
318 return;
319 }
Steve Paik4d257022018-04-27 13:28:31 -0700320 }
321 if ((client == null) || (propertyClients == null)) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800322 Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not previously registered.");
Steve Paik4d257022018-04-27 13:28:31 -0700323 } else {
324 if (propertyClients.remove(client)) {
325 client.removeProperty(propId);
Kai6e75d492020-02-20 16:11:40 -0800326 clearSetOperationRecorderLocked(propId, client);
Steve Paik4d257022018-04-27 13:28:31 -0700327 } else {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800328 Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for "
Steve Paik4d257022018-04-27 13:28:31 -0700329 + "propId=0x" + toHexString(propId));
330 }
331
332 if (propertyClients.isEmpty()) {
333 // Last listener for this property unsubscribed. Clean up
334 mHal.unsubscribeProperty(propId);
335 mPropIdClientMap.remove(propId);
Kai6e75d492020-02-20 16:11:40 -0800336 mSetOperationClientMap.remove(propId);
Steve Paik4d257022018-04-27 13:28:31 -0700337 if (mPropIdClientMap.isEmpty()) {
338 // No more properties are subscribed. Turn off the listener.
339 mHal.setListener(null);
340 mListenerIsSet = false;
341 }
342 } else {
343 // Other listeners are still subscribed. Calculate the new rate
344 float maxRate = 0;
345 for (Client c : propertyClients) {
346 float rate = c.getRate(propId);
347 if (rate > maxRate) {
348 maxRate = rate;
349 }
350 }
351 // Set the new rate
352 mHal.subscribeProperty(propId, maxRate);
353 }
354 }
355 }
356
357 /**
Kaibaeacf22020-11-15 19:55:10 -0800358 * Return the list of properties' configs that the caller may access.
Steve Paik4d257022018-04-27 13:28:31 -0700359 */
Kaibaeacf22020-11-15 19:55:10 -0800360 @NonNull
Steve Paik4d257022018-04-27 13:28:31 -0700361 @Override
362 public List<CarPropertyConfig> getPropertyList() {
Kaibaeacf22020-11-15 19:55:10 -0800363 int[] allPropId;
364 // Avoid permission checking under lock.
Kai7a4e6ea2020-06-10 02:25:37 -0700365 synchronized (mLock) {
Kaibaeacf22020-11-15 19:55:10 -0800366 allPropId = new int[mConfigs.size()];
367 for (int i = 0; i < mConfigs.size(); i++) {
368 allPropId[i] = mConfigs.keyAt(i);
369 }
Kai7a4e6ea2020-06-10 02:25:37 -0700370 }
Kaibaeacf22020-11-15 19:55:10 -0800371 return getPropertyConfigList(allPropId);
372 }
373
374 /**
375 *
376 * @param propIds Array of property Ids
377 * @return the list of properties' configs that the caller may access.
378 */
379 @NonNull
380 @Override
381 public List<CarPropertyConfig> getPropertyConfigList(int[] propIds) {
382 // Cache the granted permissions
383 Set<String> grantedPermission = new HashSet<>();
384 List<CarPropertyConfig> availableProp = new ArrayList<>();
385 if (propIds == null) {
386 return availableProp;
387 }
388 for (int propId : propIds) {
389 String readPermission = getReadPermission(propId);
390 if (readPermission == null) {
391 continue;
392 }
393 // Check if context already granted permission first
394 if (grantedPermission.contains(readPermission)
395 || ICarImpl.hasPermission(mContext, readPermission)) {
396 grantedPermission.add(readPermission);
397 synchronized (mLock) {
398 availableProp.add(mConfigs.get(propId));
399 }
Steve Paik4d257022018-04-27 13:28:31 -0700400 }
401 }
402 if (DBG) {
Kaibaeacf22020-11-15 19:55:10 -0800403 Slog.d(TAG, "getPropertyList returns " + availableProp.size() + " configs");
Steve Paik4d257022018-04-27 13:28:31 -0700404 }
Kaibaeacf22020-11-15 19:55:10 -0800405 return availableProp;
Steve Paik4d257022018-04-27 13:28:31 -0700406 }
407
408 @Override
409 public CarPropertyValue getProperty(int prop, int zone) {
Kai7a4e6ea2020-06-10 02:25:37 -0700410 synchronized (mLock) {
411 if (mConfigs.get(prop) == null) {
412 // Do not attempt to register an invalid propId
Eric Jeongbd5fb562020-12-21 13:49:40 -0800413 Slog.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop));
Kai7a4e6ea2020-06-10 02:25:37 -0700414 return null;
415 }
Steve Paik4d257022018-04-27 13:28:31 -0700416 }
417 ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop));
418 return mHal.getProperty(prop, zone);
419 }
420
Kai Wang301dd472021-05-18 06:09:44 +0000421 /**
422 * Get property value for car service's internal usage.
423 * @param prop property id
424 * @param zone area id
425 * @return null if property is not implemented or there is an exception in the vehicle.
426 */
427 public CarPropertyValue getPropertySafe(int prop, int zone) {
428 synchronized (mLock) {
429 if (mConfigs.get(prop) == null) {
430 // Do not attempt to register an invalid propId
431 Slog.e(TAG, "getPropertySafe: propId is not in config list:0x"
432 + toHexString(prop));
433 return null;
434 }
435 }
436 ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop));
437 return mHal.getPropertySafe(prop, zone);
438 }
439
Kaibaeacf22020-11-15 19:55:10 -0800440 @Nullable
Steve Paik4d257022018-04-27 13:28:31 -0700441 @Override
Steve Paik03419e52018-10-29 17:25:45 -0700442 public String getReadPermission(int propId) {
Kaibaeacf22020-11-15 19:55:10 -0800443 Pair<String, String> permissions;
Kai7a4e6ea2020-06-10 02:25:37 -0700444 synchronized (mLock) {
Kaibaeacf22020-11-15 19:55:10 -0800445 permissions = mPropToPermission.get(propId);
Steve Paik03419e52018-10-29 17:25:45 -0700446 }
Kaibaeacf22020-11-15 19:55:10 -0800447 if (permissions == null) {
448 // Property ID does not exist
449 Slog.e(TAG,
450 "getReadPermission: propId is not in config list:0x" + toHexString(propId));
451 return null;
452 }
453 return permissions.first;
Steve Paik03419e52018-10-29 17:25:45 -0700454 }
455
Kaibaeacf22020-11-15 19:55:10 -0800456 @Nullable
Steve Paik03419e52018-10-29 17:25:45 -0700457 @Override
458 public String getWritePermission(int propId) {
Kaibaeacf22020-11-15 19:55:10 -0800459 Pair<String, String> permissions;
Kai7a4e6ea2020-06-10 02:25:37 -0700460 synchronized (mLock) {
Kaibaeacf22020-11-15 19:55:10 -0800461 permissions = mPropToPermission.get(propId);
Steve Paik03419e52018-10-29 17:25:45 -0700462 }
Kaibaeacf22020-11-15 19:55:10 -0800463 if (permissions == null) {
464 // Property ID does not exist
465 Slog.e(TAG,
466 "getWritePermission: propId is not in config list:0x" + toHexString(propId));
467 return null;
468 }
469 return permissions.second;
Steve Paik03419e52018-10-29 17:25:45 -0700470 }
471
472 @Override
Kai6e75d492020-02-20 16:11:40 -0800473 public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) {
Steve Paik4d257022018-04-27 13:28:31 -0700474 int propId = prop.getPropertyId();
Kai3a8a5af2020-03-11 15:39:58 -0700475 checkPropertyAccessibility(propId);
Kaiafbb5022019-10-21 14:24:07 -0700476 // need an extra permission for writing display units properties.
477 if (mHal.isDisplayUnitsProperty(propId)) {
478 ICarImpl.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION);
479 }
Steve Paik4d257022018-04-27 13:28:31 -0700480 mHal.setProperty(prop);
Kai6e75d492020-02-20 16:11:40 -0800481 IBinder listenerBinder = listener.asBinder();
482 synchronized (mLock) {
483 Client client = mClientMap.get(listenerBinder);
484 if (client == null) {
485 client = new Client(listener);
486 }
487 updateSetOperationRecorder(propId, prop.getAreaId(), client);
488 }
489 }
490
Kai3a8a5af2020-03-11 15:39:58 -0700491 // The helper method checks if the vehicle has implemented this property and the property
492 // is accessible or not for platform and client.
493 private void checkPropertyAccessibility(int propId) {
494 // Checks if the car implemented the property or not.
Kai7a4e6ea2020-06-10 02:25:37 -0700495 synchronized (mLock) {
496 if (mConfigs.get(propId) == null) {
497 throw new IllegalArgumentException("Property Id: 0x" + Integer.toHexString(propId)
498 + " does not exist in the vehicle");
499 }
Kai3a8a5af2020-03-11 15:39:58 -0700500 }
501
502 // Checks if android has permission to write property.
503 String propertyWritePermission = mHal.getWritePermission(propId);
504 if (propertyWritePermission == null) {
505 throw new SecurityException("Platform does not have permission to change value for "
506 + "property Id: 0x" + Integer.toHexString(propId));
507 }
508 // Checks if the client has the permission.
509 ICarImpl.assertPermission(mContext, propertyWritePermission);
510 }
511
Kai6e75d492020-02-20 16:11:40 -0800512 // Updates recorder for set operation.
513 private void updateSetOperationRecorder(int propId, int areaId, Client client) {
514 if (mSetOperationClientMap.get(propId) != null) {
515 mSetOperationClientMap.get(propId).put(areaId, client);
516 } else {
517 SparseArray<Client> areaIdToClient = new SparseArray<>();
518 areaIdToClient.put(areaId, client);
519 mSetOperationClientMap.put(propId, areaIdToClient);
520 }
521 }
522
523 // Clears map when client unregister for property.
524 private void clearSetOperationRecorderLocked(int propId, Client client) {
525 SparseArray<Client> areaIdToClient = mSetOperationClientMap.get(propId);
526 if (areaIdToClient != null) {
527 List<Integer> indexNeedToRemove = new ArrayList<>();
528 for (int index = 0; index < areaIdToClient.size(); index++) {
529 if (client.equals(areaIdToClient.valueAt(index))) {
530 indexNeedToRemove.add(index);
531 }
532 }
533
534 for (int index : indexNeedToRemove) {
535 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800536 Slog.d("ErrorEvent", " Clear propId:0x" + toHexString(propId)
Kai6e75d492020-02-20 16:11:40 -0800537 + " areaId: 0x" + toHexString(areaIdToClient.keyAt(index)));
538 }
539 areaIdToClient.removeAt(index);
540 }
541 }
Steve Paik4d257022018-04-27 13:28:31 -0700542 }
543
544 // Implement PropertyHalListener interface
545 @Override
546 public void onPropertyChange(List<CarPropertyEvent> events) {
547 Map<IBinder, Pair<ICarPropertyEventListener, List<CarPropertyEvent>>> eventsToDispatch =
548 new HashMap<>();
549
550 for (CarPropertyEvent event : events) {
551 int propId = event.getCarPropertyValue().getPropertyId();
552 List<Client> clients = mPropIdClientMap.get(propId);
553 if (clients == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800554 Slog.e(TAG, "onPropertyChange: no listener registered for propId=0x"
Steve Paik4d257022018-04-27 13:28:31 -0700555 + toHexString(propId));
556 continue;
557 }
558
559 for (Client c : clients) {
560 IBinder listenerBinder = c.getListenerBinder();
561 Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p =
562 eventsToDispatch.get(listenerBinder);
563 if (p == null) {
564 // Initialize the linked list for the listener
565 p = new Pair<>(c.getListener(), new LinkedList<CarPropertyEvent>());
566 eventsToDispatch.put(listenerBinder, p);
567 }
568 p.second.add(event);
569 }
570 }
571 // Parse the dispatch list to send events
572 for (Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p: eventsToDispatch.values()) {
573 try {
574 p.first.onEvent(p.second);
575 } catch (RemoteException ex) {
576 // If we cannot send a record, its likely the connection snapped. Let binder
577 // death handle the situation.
Eric Jeongbd5fb562020-12-21 13:49:40 -0800578 Slog.e(TAG, "onEvent calling failed: " + ex);
Steve Paik4d257022018-04-27 13:28:31 -0700579 }
580 }
581 }
582
583 @Override
Kai6e75d492020-02-20 16:11:40 -0800584 public void onPropertySetError(int property, int areaId, int errorCode) {
585 Client lastOperatedClient = null;
586 synchronized (mLock) {
587 if (mSetOperationClientMap.get(property) != null
588 && mSetOperationClientMap.get(property).get(areaId) != null) {
589 lastOperatedClient = mSetOperationClientMap.get(property).get(areaId);
590 } else {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800591 Slog.e(TAG, "Can not find the client changed propertyId: 0x"
Kai6e75d492020-02-20 16:11:40 -0800592 + toHexString(property) + " in areaId: 0x" + toHexString(areaId));
Steve Paik4d257022018-04-27 13:28:31 -0700593 }
Kai6e75d492020-02-20 16:11:40 -0800594
595 }
596 if (lastOperatedClient != null) {
597 dispatchToLastClient(property, areaId, errorCode, lastOperatedClient);
598 }
599 }
600
601 private void dispatchToLastClient(int property, int areaId, int errorCode,
602 Client lastOperatedClient) {
603 try {
604 List<CarPropertyEvent> eventList = new LinkedList<>();
605 eventList.add(
606 CarPropertyEvent.createErrorEventWithErrorCode(property, areaId,
607 errorCode));
608 lastOperatedClient.getListener().onEvent(eventList);
609 } catch (RemoteException ex) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800610 Slog.e(TAG, "onEvent calling failed: " + ex);
Steve Paik4d257022018-04-27 13:28:31 -0700611 }
612 }
Steve Paik4d257022018-04-27 13:28:31 -0700613}