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 | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 21 | import android.annotation.NonNull; |
| 22 | import android.annotation.Nullable; |
Kai | afbb502 | 2019-10-21 14:24:07 -0700 | [diff] [blame] | 23 | import android.car.Car; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 24 | import android.car.hardware.CarPropertyConfig; |
| 25 | import android.car.hardware.CarPropertyValue; |
| 26 | import android.car.hardware.property.CarPropertyEvent; |
| 27 | import android.car.hardware.property.ICarProperty; |
| 28 | import android.car.hardware.property.ICarPropertyEventListener; |
| 29 | import android.content.Context; |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 30 | import android.os.Handler; |
| 31 | import android.os.HandlerThread; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 32 | import android.os.IBinder; |
| 33 | import android.os.RemoteException; |
Felipe Leme | 176a5fd | 2021-01-20 15:48:33 -0800 | [diff] [blame] | 34 | import android.util.IndentingPrintWriter; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 35 | import android.util.Pair; |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 36 | import android.util.Slog; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 37 | import android.util.SparseArray; |
| 38 | |
| 39 | import com.android.car.hal.PropertyHalService; |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 40 | import com.android.internal.annotations.GuardedBy; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 41 | |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 42 | import java.util.ArrayList; |
| 43 | import java.util.HashMap; |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 44 | import java.util.HashSet; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 45 | import java.util.LinkedList; |
| 46 | import java.util.List; |
| 47 | import java.util.Map; |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 48 | import java.util.Set; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 49 | import java.util.concurrent.ConcurrentHashMap; |
| 50 | import java.util.concurrent.CopyOnWriteArrayList; |
| 51 | |
| 52 | /** |
| 53 | * 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] | 54 | * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in |
| 55 | * manager level. |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 56 | */ |
| 57 | public class CarPropertyService extends ICarProperty.Stub |
| 58 | implements CarServiceBase, PropertyHalService.PropertyHalListener { |
| 59 | private static final boolean DBG = true; |
Mayank Garg | 72c71d2 | 2021-02-03 23:54:45 -0800 | [diff] [blame] | 60 | private static final String TAG = CarLog.tagFor(CarPropertyService.class); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 61 | private final Context mContext; |
| 62 | private final Map<IBinder, Client> mClientMap = new ConcurrentHashMap<>(); |
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()); |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 72 | // 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<>(); |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 77 | |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 78 | public CarPropertyService(Context context, PropertyHalService hal) { |
| 79 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 80 | Slog.d(TAG, "CarPropertyService started!"); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 81 | } |
| 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 Paupore | 0dd3177 | 2019-02-07 21:22:34 -0800 | [diff] [blame] | 99 | throw new IllegalStateException("Client already dead", e); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 100 | } |
| 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 115 | Slog.d(TAG, "binderDied " + mListenerBinder); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 116 | } |
| 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() { |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 154 | synchronized (mLock) { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 155 | // Cache the configs list and permissions to avoid subsequent binder calls |
| 156 | mConfigs = mHal.getPropertyList(); |
| 157 | mPropToPermission = mHal.getPermissionsForAllProperties(); |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 158 | } |
| 159 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 160 | Slog.d(TAG, "cache CarPropertyConfigs " + mConfigs.size()); |
Kai | 579b5d3 | 2018-07-20 14:25:34 -0700 | [diff] [blame] | 161 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 162 | } |
| 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; |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 173 | synchronized (mLock) { |
| 174 | mSetOperationClientMap.clear(); |
| 175 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | @Override |
Felipe Leme | 176a5fd | 2021-01-20 15:48:33 -0800 | [diff] [blame] | 179 | public void dump(IndentingPrintWriter writer) { |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 180 | writer.println("*CarPropertyService*"); |
Kai | 29043b1 | 2021-02-10 20:38:57 -0800 | [diff] [blame] | 181 | writer.increaseIndent(); |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 182 | synchronized (mLock) { |
Kai | 29043b1 | 2021-02-10 20:38:57 -0800 | [diff] [blame] | 183 | 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(); |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 189 | for (int propId : mPropIdClientMap.keySet()) { |
Kai | 29043b1 | 2021-02-10 20:38:57 -0800 | [diff] [blame] | 190 | writer.println("propId: 0x" + toHexString(propId) |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 191 | + " is registered by " + mPropIdClientMap.get(propId).size() |
| 192 | + " client(s)."); |
| 193 | } |
Kai | 29043b1 | 2021-02-10 20:38:57 -0800 | [diff] [blame] | 194 | writer.decreaseIndent(); |
| 195 | writer.println("Properties changed by CarPropertyService: "); |
| 196 | writer.increaseIndent(); |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 197 | 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); |
Kai | 29043b1 | 2021-02-10 20:38:57 -0800 | [diff] [blame] | 202 | writer.println(String.format("propId: 0x%s areaId: 0x%s by client: %s", |
| 203 | toHexString(propId), toHexString(areaId), areaIdToClient.valueAt(j))); |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 204 | } |
| 205 | } |
Kai | 29043b1 | 2021-02-10 20:38:57 -0800 | [diff] [blame] | 206 | writer.decreaseIndent(); |
Kai | 5a94d4b | 2020-05-13 22:16:41 -0700 | [diff] [blame] | 207 | } |
Kai | 29043b1 | 2021-02-10 20:38:57 -0800 | [diff] [blame] | 208 | writer.decreaseIndent(); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | @Override |
| 212 | public void registerListener(int propId, float rate, ICarPropertyEventListener listener) { |
| 213 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 214 | Slog.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 215 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 216 | if (listener == null) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 217 | Slog.e(TAG, "registerListener: Listener is null."); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 218 | throw new IllegalArgumentException("listener cannot be null."); |
| 219 | } |
| 220 | |
| 221 | IBinder listenerBinder = listener.asBinder(); |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 222 | CarPropertyConfig propertyConfig; |
| 223 | Client finalClient; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 224 | synchronized (mLock) { |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 225 | propertyConfig = mConfigs.get(propId); |
| 226 | if (propertyConfig == null) { |
| 227 | // Do not attempt to register an invalid propId |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 228 | Slog.e(TAG, "registerListener: propId is not in config list: 0x" + toHexString( |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 229 | propId)); |
| 230 | return; |
| 231 | } |
| 232 | ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId)); |
| 233 | // Get or create the client for this listener |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 234 | 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 | } |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 256 | finalClient = client; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 257 | } |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 258 | |
| 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 Wang | 301dd47 | 2021-05-18 06:09:44 +0000 | [diff] [blame] | 268 | CarPropertyValue value = mHal.getPropertySafe(propId, 0); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 269 | if (value != null) { |
| 270 | CarPropertyEvent event = new CarPropertyEvent( |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 271 | CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 272 | events.add(event); |
| 273 | } |
Kai | 79b6cfa | 2018-06-28 17:24:12 -0700 | [diff] [blame] | 274 | } else { |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 275 | for (int areaId : config.getAreaIds()) { |
Kai Wang | 301dd47 | 2021-05-18 06:09:44 +0000 | [diff] [blame] | 276 | CarPropertyValue value = mHal.getPropertySafe(propId, areaId); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 277 | if (value != null) { |
| 278 | CarPropertyEvent event = new CarPropertyEvent( |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 279 | CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); |
Kai | 5831d73 | 2019-05-14 18:10:25 -0700 | [diff] [blame] | 280 | events.add(event); |
| 281 | } |
Kai | 79b6cfa | 2018-06-28 17:24:12 -0700 | [diff] [blame] | 282 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 283 | } |
| 284 | try { |
Kai | d5d8b92 | 2020-05-12 23:07:45 -0700 | [diff] [blame] | 285 | client.getListener().onEvent(events); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 286 | } catch (RemoteException ex) { |
| 287 | // If we cannot send a record, its likely the connection snapped. Let the binder |
| 288 | // death handle the situation. |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 289 | Slog.e(TAG, "onEvent calling failed: " + ex); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 290 | } |
| 291 | } |
| 292 | |
| 293 | @Override |
| 294 | public void unregisterListener(int propId, ICarPropertyEventListener listener) { |
| 295 | if (DBG) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 296 | Slog.d(TAG, "unregisterListener propId=0x" + toHexString(propId)); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 297 | } |
| 298 | ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId)); |
| 299 | if (listener == null) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 300 | Slog.e(TAG, "unregisterListener: Listener is null."); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 301 | 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); |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 313 | synchronized (mLock) { |
| 314 | if (mConfigs.get(propId) == null) { |
| 315 | // Do not attempt to register an invalid propId |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 316 | Slog.e(TAG, "unregisterListener: propId is not in config list:0x" + toHexString( |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 317 | propId)); |
| 318 | return; |
| 319 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 320 | } |
| 321 | if ((client == null) || (propertyClients == null)) { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 322 | Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not previously registered."); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 323 | } else { |
| 324 | if (propertyClients.remove(client)) { |
| 325 | client.removeProperty(propId); |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 326 | clearSetOperationRecorderLocked(propId, client); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 327 | } else { |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 328 | Slog.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for " |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 329 | + "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); |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 336 | mSetOperationClientMap.remove(propId); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 337 | 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 | /** |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 358 | * Return the list of properties' configs that the caller may access. |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 359 | */ |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 360 | @NonNull |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 361 | @Override |
| 362 | public List<CarPropertyConfig> getPropertyList() { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 363 | int[] allPropId; |
| 364 | // Avoid permission checking under lock. |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 365 | synchronized (mLock) { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 366 | allPropId = new int[mConfigs.size()]; |
| 367 | for (int i = 0; i < mConfigs.size(); i++) { |
| 368 | allPropId[i] = mConfigs.keyAt(i); |
| 369 | } |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 370 | } |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 371 | 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 Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 400 | } |
| 401 | } |
| 402 | if (DBG) { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 403 | Slog.d(TAG, "getPropertyList returns " + availableProp.size() + " configs"); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 404 | } |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 405 | return availableProp; |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 406 | } |
| 407 | |
| 408 | @Override |
| 409 | public CarPropertyValue getProperty(int prop, int zone) { |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 410 | synchronized (mLock) { |
| 411 | if (mConfigs.get(prop) == null) { |
| 412 | // Do not attempt to register an invalid propId |
Eric Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 413 | Slog.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop)); |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 414 | return null; |
| 415 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 416 | } |
| 417 | ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop)); |
| 418 | return mHal.getProperty(prop, zone); |
| 419 | } |
| 420 | |
Kai Wang | 301dd47 | 2021-05-18 06:09:44 +0000 | [diff] [blame] | 421 | /** |
| 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 | |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 440 | @Nullable |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 441 | @Override |
Steve Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 442 | public String getReadPermission(int propId) { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 443 | Pair<String, String> permissions; |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 444 | synchronized (mLock) { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 445 | permissions = mPropToPermission.get(propId); |
Steve Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 446 | } |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 447 | 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 Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 454 | } |
| 455 | |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 456 | @Nullable |
Steve Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 457 | @Override |
| 458 | public String getWritePermission(int propId) { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 459 | Pair<String, String> permissions; |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 460 | synchronized (mLock) { |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 461 | permissions = mPropToPermission.get(propId); |
Steve Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 462 | } |
Kai | baeacf2 | 2020-11-15 19:55:10 -0800 | [diff] [blame] | 463 | 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 Paik | 03419e5 | 2018-10-29 17:25:45 -0700 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | @Override |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 473 | public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) { |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 474 | int propId = prop.getPropertyId(); |
Kai | 3a8a5af | 2020-03-11 15:39:58 -0700 | [diff] [blame] | 475 | checkPropertyAccessibility(propId); |
Kai | afbb502 | 2019-10-21 14:24:07 -0700 | [diff] [blame] | 476 | // need an extra permission for writing display units properties. |
| 477 | if (mHal.isDisplayUnitsProperty(propId)) { |
| 478 | ICarImpl.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION); |
| 479 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 480 | mHal.setProperty(prop); |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 481 | 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 | |
Kai | 3a8a5af | 2020-03-11 15:39:58 -0700 | [diff] [blame] | 491 | // 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. |
Kai | 7a4e6ea | 2020-06-10 02:25:37 -0700 | [diff] [blame] | 495 | 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 | } |
Kai | 3a8a5af | 2020-03-11 15:39:58 -0700 | [diff] [blame] | 500 | } |
| 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 | |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 512 | // 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 536 | Slog.d("ErrorEvent", " Clear propId:0x" + toHexString(propId) |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 537 | + " areaId: 0x" + toHexString(areaIdToClient.keyAt(index))); |
| 538 | } |
| 539 | areaIdToClient.removeAt(index); |
| 540 | } |
| 541 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 542 | } |
| 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 554 | Slog.e(TAG, "onPropertyChange: no listener registered for propId=0x" |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 555 | + 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 578 | Slog.e(TAG, "onEvent calling failed: " + ex); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 579 | } |
| 580 | } |
| 581 | } |
| 582 | |
| 583 | @Override |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 584 | 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 591 | Slog.e(TAG, "Can not find the client changed propertyId: 0x" |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 592 | + toHexString(property) + " in areaId: 0x" + toHexString(areaId)); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 593 | } |
Kai | 6e75d49 | 2020-02-20 16:11:40 -0800 | [diff] [blame] | 594 | |
| 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 Jeong | bd5fb56 | 2020-12-21 13:49:40 -0800 | [diff] [blame] | 610 | Slog.e(TAG, "onEvent calling failed: " + ex); |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 611 | } |
| 612 | } |
Steve Paik | 4d25702 | 2018-04-27 13:28:31 -0700 | [diff] [blame] | 613 | } |