1 /* 2 * Copyright (C) 2022 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.internal; 18 19 import static java.util.Objects.requireNonNull; 20 21 import android.annotation.Nullable; 22 import android.car.VehiclePropertyIds; 23 import android.car.hardware.CarPropertyValue; 24 import android.car.hardware.property.CarPropertyEvent; 25 import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback; 26 import android.util.ArrayMap; 27 import android.util.Log; 28 import android.util.SparseLongArray; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Objects; 37 38 /** 39 * Manages a group of {@link CarPropertyEventCallback} instances registered for the same {@link 40 * #mPropertyId} at possibly different update rates. 41 * 42 * @hide 43 */ 44 public final class CarPropertyEventCallbackController { 45 // Abbreviating TAG because class name is longer than the 23 character Log tag limit. 46 private static final String TAG = "CPECallbackController"; 47 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 48 private static final float NANOSECOND_PER_SECOND = 1000 * 1000 * 1000; 49 // Since this class is internal to CarPropertyManager, it shares the same lock to avoid 50 // potential deadlock. 51 private final Object mCarPropertyManagerLock; 52 @GuardedBy("mCarPropertyManagerLock") 53 private final Map<CarPropertyEventCallback, Float> mCarPropertyEventCallbackToUpdateRateHz = 54 new ArrayMap<>(); 55 @GuardedBy("mCarPropertyManagerLock") 56 private final Map<CarPropertyEventCallback, SparseLongArray> 57 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos = new ArrayMap<>(); 58 private final int mPropertyId; 59 private final RegistrationUpdateCallback mRegistrationUpdateCallback; 60 @GuardedBy("mCarPropertyManagerLock") 61 private Float mMaxUpdateRateHz = null; 62 CarPropertyEventCallbackController(int propertyId, Object carPropertyManagerLock, RegistrationUpdateCallback registrationUpdateCallback)63 public CarPropertyEventCallbackController(int propertyId, Object carPropertyManagerLock, 64 RegistrationUpdateCallback registrationUpdateCallback) { 65 requireNonNull(registrationUpdateCallback); 66 mPropertyId = propertyId; 67 mCarPropertyManagerLock = carPropertyManagerLock; 68 mRegistrationUpdateCallback = registrationUpdateCallback; 69 } 70 71 /** 72 * Forward a successful {@link CarPropertyEvent} to the registered {@link 73 * CarPropertyEventCallback} instances if the {@link CarPropertyEventCallback} instance's update 74 * rate threshold is met. 75 */ forwardPropertyChanged(CarPropertyEvent carPropertyEvent)76 public void forwardPropertyChanged(CarPropertyEvent carPropertyEvent) { 77 requireNonNull(carPropertyEvent); 78 List<CarPropertyEventCallback> carPropertyEventCallbacks = getCallbacksForCarPropertyEvent( 79 carPropertyEvent); 80 CarPropertyValue<?> carPropertyValue = carPropertyEvent.getCarPropertyValue(); 81 for (int i = 0; i < carPropertyEventCallbacks.size(); i++) { 82 carPropertyEventCallbacks.get(i).onChangeEvent(carPropertyValue); 83 } 84 } 85 86 /** 87 * Forward an error {@link CarPropertyEvent} to the registered {@link CarPropertyEventCallback} 88 * instances. 89 */ forwardErrorEvent(CarPropertyEvent carPropertyEvent)90 public void forwardErrorEvent(CarPropertyEvent carPropertyEvent) { 91 requireNonNull(carPropertyEvent); 92 CarPropertyValue<?> carPropertyValue = carPropertyEvent.getCarPropertyValue(); 93 if (DBG) { 94 Log.d(TAG, "onErrorEvent for property: " + VehiclePropertyIds.toString( 95 carPropertyValue.getPropertyId()) + " areaId: " + carPropertyValue.getAreaId() 96 + " errorCode: " + carPropertyEvent.getErrorCode()); 97 } 98 List<CarPropertyEventCallback> carPropertyEventCallbacks; 99 synchronized (mCarPropertyManagerLock) { 100 carPropertyEventCallbacks = new ArrayList<>( 101 mCarPropertyEventCallbackToUpdateRateHz.keySet()); 102 } 103 for (int i = 0; i < carPropertyEventCallbacks.size(); i++) { 104 carPropertyEventCallbacks.get(i).onErrorEvent(carPropertyValue.getPropertyId(), 105 carPropertyValue.getAreaId(), carPropertyEvent.getErrorCode()); 106 107 } 108 } 109 110 /** 111 * Add given {@link CarPropertyEventCallback} to the list and update registration if necessary. 112 * 113 * @return true is registration was successful, otherwise false. 114 */ add(CarPropertyEventCallback carPropertyEventCallback, float updateRateHz)115 public boolean add(CarPropertyEventCallback carPropertyEventCallback, float updateRateHz) { 116 requireNonNull(carPropertyEventCallback); 117 synchronized (mCarPropertyManagerLock) { 118 Float previousUpdateRateHz = mCarPropertyEventCallbackToUpdateRateHz.put( 119 carPropertyEventCallback, updateRateHz); 120 SparseLongArray previousAreaIdToNextUpdateTimeNanos = 121 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.put( 122 carPropertyEventCallback, new SparseLongArray()); 123 return updateMaxUpdateRateHzAndRegisterLocked(carPropertyEventCallback, 124 previousUpdateRateHz, previousAreaIdToNextUpdateTimeNanos); 125 } 126 } 127 128 /** 129 * Remove given {@link CarPropertyEventCallback} from the list and update registration if 130 * necessary. 131 * 132 * @return true if all {@link CarPropertyEventCallback} instances are removed, otherwise false. 133 */ remove(CarPropertyEventCallback carPropertyEventCallback)134 public boolean remove(CarPropertyEventCallback carPropertyEventCallback) { 135 requireNonNull(carPropertyEventCallback); 136 synchronized (mCarPropertyManagerLock) { 137 Float previousUpdateRateHz = 138 mCarPropertyEventCallbackToUpdateRateHz.remove(carPropertyEventCallback); 139 SparseLongArray previousAreaIdToNextUpdateTimeNanos = 140 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.remove( 141 carPropertyEventCallback); 142 updateMaxUpdateRateHzAndRegisterLocked(carPropertyEventCallback, 143 previousUpdateRateHz, previousAreaIdToNextUpdateTimeNanos); 144 return mCarPropertyEventCallbackToUpdateRateHz.isEmpty(); 145 } 146 } 147 getCallbacksForCarPropertyEvent( CarPropertyEvent carPropertyEvent)148 private List<CarPropertyEventCallback> getCallbacksForCarPropertyEvent( 149 CarPropertyEvent carPropertyEvent) { 150 List<CarPropertyEventCallback> carPropertyEventCallbacks = new ArrayList<>(); 151 synchronized (mCarPropertyManagerLock) { 152 for (CarPropertyEventCallback carPropertyEventCallback : 153 mCarPropertyEventCallbackToUpdateRateHz.keySet()) { 154 if (shouldCallbackBeInvokedLocked(carPropertyEventCallback, carPropertyEvent)) { 155 carPropertyEventCallbacks.add(carPropertyEventCallback); 156 } 157 } 158 } 159 return carPropertyEventCallbacks; 160 } 161 162 @GuardedBy("mCarPropertyManagerLock") shouldCallbackBeInvokedLocked(CarPropertyEventCallback carPropertyEventCallback, CarPropertyEvent carPropertyEvent)163 private boolean shouldCallbackBeInvokedLocked(CarPropertyEventCallback carPropertyEventCallback, 164 CarPropertyEvent carPropertyEvent) { 165 SparseLongArray areaIdToNextUpdateTimeNanos = 166 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.get( 167 carPropertyEventCallback); 168 Float updateRateHz = mCarPropertyEventCallbackToUpdateRateHz.get(carPropertyEventCallback); 169 if (areaIdToNextUpdateTimeNanos == null || updateRateHz == null) { 170 CarPropertyValue<?> carPropertyValue = carPropertyEvent.getCarPropertyValue(); 171 Log.w(TAG, "callback was not found for property: " + VehiclePropertyIds.toString( 172 carPropertyValue.getPropertyId()) + " areaId: " + carPropertyValue.getAreaId() 173 + " timestampNanos: " + carPropertyValue.getTimestamp()); 174 return false; 175 } 176 177 CarPropertyValue<?> carPropertyValue = carPropertyEvent.getCarPropertyValue(); 178 long nextUpdateTimeNanos = areaIdToNextUpdateTimeNanos.get(carPropertyValue.getAreaId(), 179 0L); 180 181 if (carPropertyValue.getTimestamp() >= nextUpdateTimeNanos) { 182 long updatePeriodNanos = 183 updateRateHz > 0 ? ((long) ((1.0 / updateRateHz) * NANOSECOND_PER_SECOND)) : 0; 184 areaIdToNextUpdateTimeNanos.put(carPropertyValue.getAreaId(), 185 carPropertyValue.getTimestamp() + updatePeriodNanos); 186 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.put(carPropertyEventCallback, 187 areaIdToNextUpdateTimeNanos); 188 return true; 189 } 190 191 if (DBG) { 192 Log.d(TAG, "Dropping carPropertyEvent - propId: " + carPropertyValue.getPropertyId() 193 + " areaId: " + carPropertyValue.getAreaId() + " because getTimestamp(): " 194 + carPropertyValue.getTimestamp() + " < nextUpdateTimeNanos: " 195 + nextUpdateTimeNanos); 196 } 197 return false; 198 } 199 200 @GuardedBy("mCarPropertyManagerLock") restorePreviousUpdateValuesLocked( CarPropertyEventCallback carPropertyEventCallback, @Nullable Float previousUpdateRateHz, @Nullable SparseLongArray previousAreaIdToNextUpdateTimeNanos)201 private void restorePreviousUpdateValuesLocked( 202 CarPropertyEventCallback carPropertyEventCallback, @Nullable Float previousUpdateRateHz, 203 @Nullable SparseLongArray previousAreaIdToNextUpdateTimeNanos) { 204 if (previousUpdateRateHz != null && previousAreaIdToNextUpdateTimeNanos != null) { 205 mCarPropertyEventCallbackToUpdateRateHz.put(carPropertyEventCallback, 206 previousUpdateRateHz); 207 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.put( 208 carPropertyEventCallback, previousAreaIdToNextUpdateTimeNanos); 209 } else { 210 mCarPropertyEventCallbackToUpdateRateHz.remove(carPropertyEventCallback); 211 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.remove( 212 carPropertyEventCallback); 213 } 214 mMaxUpdateRateHz = calculateMaxUpdateRateHzLocked(); 215 } 216 217 @GuardedBy("mCarPropertyManagerLock") updateMaxUpdateRateHzAndRegisterLocked( CarPropertyEventCallback carPropertyEventCallback, @Nullable Float previousUpdateRateHz, @Nullable SparseLongArray previousAreaIdToNextUpdateTimeNanos)218 private boolean updateMaxUpdateRateHzAndRegisterLocked( 219 CarPropertyEventCallback carPropertyEventCallback, @Nullable Float previousUpdateRateHz, 220 @Nullable SparseLongArray previousAreaIdToNextUpdateTimeNanos) 221 throws SecurityException { 222 Float newMaxUpdateRateHz = calculateMaxUpdateRateHzLocked(); 223 if (Objects.equals(mMaxUpdateRateHz, newMaxUpdateRateHz)) { 224 return true; 225 } 226 mMaxUpdateRateHz = newMaxUpdateRateHz; 227 228 boolean updateSuccessful; 229 try { 230 if (newMaxUpdateRateHz == null) { 231 updateSuccessful = mRegistrationUpdateCallback.unregister(mPropertyId); 232 } else { 233 updateSuccessful = 234 mRegistrationUpdateCallback.register(mPropertyId, newMaxUpdateRateHz); 235 } 236 } catch (SecurityException e) { 237 restorePreviousUpdateValuesLocked(carPropertyEventCallback, previousUpdateRateHz, 238 previousAreaIdToNextUpdateTimeNanos); 239 throw e; 240 } 241 242 if (!updateSuccessful) { 243 restorePreviousUpdateValuesLocked(carPropertyEventCallback, previousUpdateRateHz, 244 previousAreaIdToNextUpdateTimeNanos); 245 } 246 return updateSuccessful; 247 } 248 249 @GuardedBy("mCarPropertyManagerLock") 250 @Nullable calculateMaxUpdateRateHzLocked()251 private Float calculateMaxUpdateRateHzLocked() { 252 if (mCarPropertyEventCallbackToUpdateRateHz.isEmpty()) { 253 return null; 254 } 255 return Collections.max(mCarPropertyEventCallbackToUpdateRateHz.values()); 256 } 257 258 /** 259 * Interface that receives updates to register or unregister property with {@link 260 * com.android.car.CarPropertyService}. 261 */ 262 public interface RegistrationUpdateCallback { 263 /** 264 * Called when {@code propertyId} registration needs to be updated. 265 * 266 * @return {@code true} if registration was successful, otherwise false. 267 * @throws SecurityException if missing the appropriate permission. 268 */ register(int propertyId, float updateRateHz)269 boolean register(int propertyId, float updateRateHz); 270 271 /** 272 * Called when {@code propertyId} needs to be unregistered. 273 * 274 * @return {@code true} if deregistration was successful, otherwise false. 275 * @throws SecurityException if missing the appropriate permission. 276 */ unregister(int propertyId)277 boolean unregister(int propertyId); 278 } 279 } 280 281