Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 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 java.lang.Integer.toHexString; |
| 20 | |
Kai | afbb502 | 2019-10-21 14:24:07 -0700 | [diff] [blame] | 21 | import android.car.Car; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 22 | import android.car.hardware.CarPropertyConfig; |
| 23 | import android.car.hardware.CarPropertyValue; |
| 24 | import android.car.hardware.property.CarPropertyEvent; |
| 25 | import android.car.hardware.property.ICarProperty; |
| 26 | import android.car.hardware.property.ICarPropertyEventListener; |
| 27 | import android.content.Context; |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 28 | import android.os.Handler; |
| 29 | import android.os.HandlerThread; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 30 | import android.os.IBinder; |
| 31 | import android.os.RemoteException; |
Felipe Leme | 176a5fd | 2021-01-20 15:48:33 -0800 | [diff] [blame] | 32 | import android.util.IndentingPrintWriter; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 33 | import android.util.Pair; |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 34 | import android.util.Slog; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 35 | import android.util.SparseArray; |
| 36 | |
| 37 | import com.android.car.hal.PropertyHalService; |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 38 | import com.android.internal.annotations.GuardedBy; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 39 | |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 40 | import java.util.ArrayList; |
| 41 | import java.util.HashMap; |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 42 | import java.util.HashSet; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 43 | import java.util.LinkedList; |
| 44 | import java.util.List; |
| 45 | import java.util.Map; |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 46 | import java.util.Set; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 47 | import java.util.concurrent.ConcurrentHashMap; |
| 48 | import java.util.concurrent.CopyOnWriteArrayList; |
| 49 | |
| 50 | /** |
| 51 | * This class implements the binder interface for ICarProperty.aidl to make it easier to create |
Kai | 3a8a5af | 2020-03-11 15:39:58 -0700 | [diff] [blame] | 52 | * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in |
| 53 | * manager level. |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 54 | */ |
| 55 | public class CarPropertyService extends ICarProperty.Stub |
| 56 | implements CarServiceBase, PropertyHalService.PropertyHalListener { |
| 57 | private static final boolean DBG = true; |
Mayank Garg | 72c71d2 | 2021-02-03 23:54:45 -0800 | [diff] [blame^] | 58 | private static final String TAG = CarLog.tagFor(CarPropertyService.class); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 59 | private final Context mContext; |
| 60 | private final Map<IBinder, Client> mClientMap = new ConcurrentHashMap<>(); |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 61 | @GuardedBy("mLock") |
| 62 | private final Map<Integer, CarPropertyConfig<?>> mConfigs = new HashMap<>(); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 63 | 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(); |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 67 | @GuardedBy("mLock") |
| 68 | private final SparseArray<SparseArray<Client>> mSetOperationClientMap = new SparseArray<>(); |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 69 | private final HandlerThread mHandlerThread = |
| 70 | CarServiceUtils.getHandlerThread(getClass().getSimpleName()); |
| 71 | private final Handler mHandler = new Handler(mHandlerThread.getLooper()); |
| 72 | |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 73 | public CarPropertyService(Context context, PropertyHalService hal) { |
| 74 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 75 | Slog.d(TAG, "CarPropertyService started!"); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 76 | } |
| 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 Paupore | 0dd3177 | 2019-02-07 21:22:34 -0800 | [diff] [blame] | 94 | throw new IllegalStateException("Client already dead", e); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 95 | } |
| 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 110 | Slog.d(TAG, "binderDied " + mListenerBinder); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 111 | } |
| 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() { |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 149 | synchronized (mLock) { |
Kai | 579b5d3 | 2018-07-20 14:25:34 -0700 | [diff] [blame] | 150 | // Cache the configs list to avoid subsequent binder calls |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 151 | mConfigs.clear(); |
| 152 | mConfigs.putAll(mHal.getPropertyList()); |
| 153 | } |
| 154 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 155 | Slog.d(TAG, "cache CarPropertyConfigs " + mConfigs.size()); |
Kai | 579b5d3 | 2018-07-20 14:25:34 -0700 | [diff] [blame] | 156 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 157 | } |
| 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; |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 168 | synchronized (mLock) { |
| 169 | mSetOperationClientMap.clear(); |
| 170 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | @Override |
Felipe Leme | 176a5fd | 2021-01-20 15:48:33 -0800 | [diff] [blame] | 174 | public void dump(IndentingPrintWriter writer) { |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 175 | 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 Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 198 | } |
| 199 | |
| 200 | @Override |
| 201 | public void registerListener(int propId, float rate, ICarPropertyEventListener listener) { |
| 202 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 203 | Slog.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 204 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 205 | if (listener == null) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 206 | Slog.e(TAG, "registerListener: Listener is null."); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 207 | throw new IllegalArgumentException("listener cannot be null."); |
| 208 | } |
| 209 | |
| 210 | IBinder listenerBinder = listener.asBinder(); |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 211 | CarPropertyConfig propertyConfig; |
| 212 | Client finalClient; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 213 | synchronized (mLock) { |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 214 | propertyConfig = mConfigs.get(propId); |
| 215 | if (propertyConfig == null) { |
| 216 | // Do not attempt to register an invalid propId |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 217 | Slog.e(TAG, "registerListener: propId is not in config list: 0x" + toHexString( |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 218 | propId)); |
| 219 | return; |
| 220 | } |
| 221 | ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId)); |
| 222 | // Get or create the client for this listener |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 223 | 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 | } |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 245 | finalClient = client; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 246 | } |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 247 | |
| 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()) { |
Kai | 79b6cfa | 2018-06-28 17:24:12 -0700 | [diff] [blame] | 257 | CarPropertyValue value = mHal.getProperty(propId, 0); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 258 | if (value != null) { |
| 259 | CarPropertyEvent event = new CarPropertyEvent( |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 260 | CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 261 | events.add(event); |
| 262 | } |
Kai | 79b6cfa | 2018-06-28 17:24:12 -0700 | [diff] [blame] | 263 | } else { |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 264 | for (int areaId : config.getAreaIds()) { |
Kai | 79b6cfa | 2018-06-28 17:24:12 -0700 | [diff] [blame] | 265 | CarPropertyValue value = mHal.getProperty(propId, areaId); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 266 | if (value != null) { |
| 267 | CarPropertyEvent event = new CarPropertyEvent( |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 268 | CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 269 | events.add(event); |
| 270 | } |
Kai | 79b6cfa | 2018-06-28 17:24:12 -0700 | [diff] [blame] | 271 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 272 | } |
| 273 | try { |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 274 | client.getListener().onEvent(events); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 275 | } catch (RemoteException ex) { |
| 276 | // If we cannot send a record, its likely the connection snapped. Let the binder |
| 277 | // death handle the situation. |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 278 | Slog.e(TAG, "onEvent calling failed: " + ex); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 279 | } |
| 280 | } |
| 281 | |
| 282 | @Override |
| 283 | public void unregisterListener(int propId, ICarPropertyEventListener listener) { |
| 284 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 285 | Slog.d(TAG, "unregisterListener propId=0x" + toHexString(propId)); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 286 | } |
| 287 | ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId)); |
| 288 | if (listener == null) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 289 | Slog.e(TAG, "unregisterListener: Listener is null."); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 290 | 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); |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 302 | synchronized (mLock) { |
| 303 | if (mConfigs.get(propId) == null) { |
| 304 | // Do not attempt to register an invalid propId |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 305 | Slog.e(TAG, "unregisterListener: propId is not in config list:0x" + toHexString( |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 306 | propId)); |
| 307 | return; |
| 308 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 309 | } |
| 310 | if ((client == null) || (propertyClients == null)) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 311 | Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not previously registered."); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 312 | } else { |
| 313 | if (propertyClients.remove(client)) { |
| 314 | client.removeProperty(propId); |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 315 | clearSetOperationRecorderLocked(propId, client); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 316 | } else { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 317 | Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for " |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 318 | + "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); |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 325 | mSetOperationClientMap.remove(propId); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 326 | 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 Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 348 | */ |
| 349 | @Override |
| 350 | public List<CarPropertyConfig> getPropertyList() { |
| 351 | List<CarPropertyConfig> returnList = new ArrayList<CarPropertyConfig>(); |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 352 | Set<CarPropertyConfig> allConfigs; |
| 353 | synchronized (mLock) { |
| 354 | allConfigs = new HashSet<>(mConfigs.values()); |
| 355 | } |
| 356 | for (CarPropertyConfig c : allConfigs) { |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 357 | 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 363 | Slog.d(TAG, "getPropertyList returns " + returnList.size() + " configs"); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 364 | } |
| 365 | return returnList; |
| 366 | } |
| 367 | |
| 368 | @Override |
| 369 | public CarPropertyValue getProperty(int prop, int zone) { |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 370 | synchronized (mLock) { |
| 371 | if (mConfigs.get(prop) == null) { |
| 372 | // Do not attempt to register an invalid propId |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 373 | Slog.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop)); |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 374 | return null; |
| 375 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 376 | } |
| 377 | ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop)); |
| 378 | return mHal.getProperty(prop, zone); |
| 379 | } |
| 380 | |
| 381 | @Override |
Steve Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 382 | public String getReadPermission(int propId) { |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 383 | synchronized (mLock) { |
| 384 | if (mConfigs.get(propId) == null) { |
| 385 | // Property ID does not exist |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 386 | Slog.e(TAG, |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 387 | "getReadPermission: propId is not in config list:0x" + toHexString(propId)); |
| 388 | return null; |
| 389 | } |
Steve Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 390 | } |
| 391 | return mHal.getReadPermission(propId); |
| 392 | } |
| 393 | |
| 394 | @Override |
| 395 | public String getWritePermission(int propId) { |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 396 | synchronized (mLock) { |
| 397 | if (mConfigs.get(propId) == null) { |
| 398 | // Property ID does not exist |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 399 | Slog.e(TAG, "getWritePermission: propId is not in config list:0x" + toHexString( |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 400 | propId)); |
| 401 | return null; |
| 402 | } |
Steve Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 403 | } |
| 404 | return mHal.getWritePermission(propId); |
| 405 | } |
| 406 | |
| 407 | @Override |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 408 | public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) { |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 409 | int propId = prop.getPropertyId(); |
Kai | 3a8a5af | 2020-03-11 15:39:58 -0700 | [diff] [blame] | 410 | checkPropertyAccessibility(propId); |
Kai | afbb502 | 2019-10-21 14:24:07 -0700 | [diff] [blame] | 411 | // need an extra permission for writing display units properties. |
| 412 | if (mHal.isDisplayUnitsProperty(propId)) { |
| 413 | ICarImpl.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION); |
| 414 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 415 | mHal.setProperty(prop); |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 416 | 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 | |
Kai | 3a8a5af | 2020-03-11 15:39:58 -0700 | [diff] [blame] | 426 | // 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. |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 430 | 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 | } |
Kai | 3a8a5af | 2020-03-11 15:39:58 -0700 | [diff] [blame] | 435 | } |
| 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 | |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 447 | // 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 471 | Slog.d("ErrorEvent", " Clear propId:0x" + toHexString(propId) |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 472 | + " areaId: 0x" + toHexString(areaIdToClient.keyAt(index))); |
| 473 | } |
| 474 | areaIdToClient.removeAt(index); |
| 475 | } |
| 476 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 477 | } |
| 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 489 | Slog.e(TAG, "onPropertyChange: no listener registered for propId=0x" |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 490 | + 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 513 | Slog.e(TAG, "onEvent calling failed: " + ex); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 514 | } |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | @Override |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 519 | 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 526 | Slog.e(TAG, "Can not find the client changed propertyId: 0x" |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 527 | + toHexString(property) + " in areaId: 0x" + toHexString(areaId)); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 528 | } |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 529 | |
| 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 545 | Slog.e(TAG, "onEvent calling failed: " + ex); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 546 | } |
| 547 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 548 | } |