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 Float previousUpdateRateHz; 118 SparseLongArray previousAreaIdToNextUpdateTimeNanos; 119 synchronized (mCarPropertyManagerLock) { 120 previousUpdateRateHz = mCarPropertyEventCallbackToUpdateRateHz.put( 121 carPropertyEventCallback, updateRateHz); 122 previousAreaIdToNextUpdateTimeNanos = 123 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.put( 124 carPropertyEventCallback, new SparseLongArray()); 125 } 126 boolean registerSuccessful = updateMaxUpdateRateHzAndRegistration(); 127 if (!registerSuccessful) { 128 synchronized (mCarPropertyManagerLock) { 129 if (previousUpdateRateHz != null && previousAreaIdToNextUpdateTimeNanos != null) { 130 mCarPropertyEventCallbackToUpdateRateHz.put(carPropertyEventCallback, 131 previousUpdateRateHz); 132 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.put( 133 carPropertyEventCallback, previousAreaIdToNextUpdateTimeNanos); 134 } else { 135 mCarPropertyEventCallbackToUpdateRateHz.remove(carPropertyEventCallback); 136 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.remove( 137 carPropertyEventCallback); 138 } 139 mMaxUpdateRateHz = calculateMaxUpdateRateHzLocked(); 140 } 141 } 142 return registerSuccessful; 143 } 144 145 /** 146 * Remove given {@link CarPropertyEventCallback} from the list and update registration if 147 * necessary. 148 * 149 * @return true if all {@link CarPropertyEventCallback} instances are removed, otherwise false. 150 */ remove(CarPropertyEventCallback carPropertyEventCallback)151 public boolean remove(CarPropertyEventCallback carPropertyEventCallback) { 152 requireNonNull(carPropertyEventCallback); 153 synchronized (mCarPropertyManagerLock) { 154 mCarPropertyEventCallbackToUpdateRateHz.remove(carPropertyEventCallback); 155 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.remove(carPropertyEventCallback); 156 } 157 updateMaxUpdateRateHzAndRegistration(); 158 synchronized (mCarPropertyManagerLock) { 159 return mCarPropertyEventCallbackToUpdateRateHz.isEmpty(); 160 } 161 } 162 163 getCallbacksForCarPropertyEvent( CarPropertyEvent carPropertyEvent)164 private List<CarPropertyEventCallback> getCallbacksForCarPropertyEvent( 165 CarPropertyEvent carPropertyEvent) { 166 List<CarPropertyEventCallback> carPropertyEventCallbacks = new ArrayList<>(); 167 synchronized (mCarPropertyManagerLock) { 168 for (CarPropertyEventCallback carPropertyEventCallback : 169 mCarPropertyEventCallbackToUpdateRateHz.keySet()) { 170 if (shouldCallbackBeInvokedLocked(carPropertyEventCallback, carPropertyEvent)) { 171 carPropertyEventCallbacks.add(carPropertyEventCallback); 172 } 173 } 174 } 175 return carPropertyEventCallbacks; 176 } 177 178 @GuardedBy("mCarPropertyManagerLock") shouldCallbackBeInvokedLocked(CarPropertyEventCallback carPropertyEventCallback, CarPropertyEvent carPropertyEvent)179 private boolean shouldCallbackBeInvokedLocked(CarPropertyEventCallback carPropertyEventCallback, 180 CarPropertyEvent carPropertyEvent) { 181 SparseLongArray areaIdToNextUpdateTimeNanos = 182 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.get( 183 carPropertyEventCallback); 184 Float updateRateHz = mCarPropertyEventCallbackToUpdateRateHz.get(carPropertyEventCallback); 185 if (areaIdToNextUpdateTimeNanos == null || updateRateHz == null) { 186 CarPropertyValue<?> carPropertyValue = carPropertyEvent.getCarPropertyValue(); 187 Log.w(TAG, "callback was not found for property: " + VehiclePropertyIds.toString( 188 carPropertyValue.getPropertyId()) + " areaId: " + carPropertyValue.getAreaId() 189 + " timestampNanos: " + carPropertyValue.getTimestamp()); 190 return false; 191 } 192 193 CarPropertyValue<?> carPropertyValue = carPropertyEvent.getCarPropertyValue(); 194 long nextUpdateTimeNanos = areaIdToNextUpdateTimeNanos.get(carPropertyValue.getAreaId(), 195 0L); 196 197 if (carPropertyValue.getTimestamp() >= nextUpdateTimeNanos) { 198 long updatePeriodNanos = 199 updateRateHz > 0 ? ((long) ((1.0 / updateRateHz) * NANOSECOND_PER_SECOND)) : 0; 200 areaIdToNextUpdateTimeNanos.put(carPropertyValue.getAreaId(), 201 carPropertyValue.getTimestamp() + updatePeriodNanos); 202 mCarPropertyEventCallbackToAreaIdToNextUpdateTimeNanos.put(carPropertyEventCallback, 203 areaIdToNextUpdateTimeNanos); 204 return true; 205 } 206 207 if (DBG) { 208 Log.d(TAG, "Dropping carPropertyEvent - propId: " + carPropertyValue.getPropertyId() 209 + " areaId: " + carPropertyValue.getAreaId() + " because getTimestamp(): " 210 + carPropertyValue.getTimestamp() + " < nextUpdateTimeNanos: " 211 + nextUpdateTimeNanos); 212 } 213 return false; 214 } 215 updateMaxUpdateRateHzAndRegistration()216 private boolean updateMaxUpdateRateHzAndRegistration() { 217 Float newMaxUpdateRateHz; 218 synchronized (mCarPropertyManagerLock) { 219 newMaxUpdateRateHz = calculateMaxUpdateRateHzLocked(); 220 if (Objects.equals(mMaxUpdateRateHz, newMaxUpdateRateHz)) { 221 return true; 222 } 223 mMaxUpdateRateHz = newMaxUpdateRateHz; 224 } 225 if (newMaxUpdateRateHz == null) { 226 mRegistrationUpdateCallback.unregister(mPropertyId); 227 return true; 228 } 229 return mRegistrationUpdateCallback.register(mPropertyId, newMaxUpdateRateHz); 230 } 231 232 @GuardedBy("mCarPropertyManagerLock") 233 @Nullable calculateMaxUpdateRateHzLocked()234 private Float calculateMaxUpdateRateHzLocked() { 235 if (mCarPropertyEventCallbackToUpdateRateHz.isEmpty()) { 236 return null; 237 } 238 return Collections.max(mCarPropertyEventCallbackToUpdateRateHz.values()); 239 } 240 241 /** 242 * Interface that receives updates to register or unregister property with {@link 243 * com.android.car.CarPropertyService}. 244 */ 245 public interface RegistrationUpdateCallback { 246 /** 247 * Called when {@code propertyId} registration needs to be updated. 248 * 249 * @return true is registration was successful, otherwise false. 250 */ register(int propertyId, float updateRateHz)251 boolean register(int propertyId, float updateRateHz); 252 253 /** 254 * Called when {@code propertyId} needs to be unregistered. 255 */ unregister(int propertyId)256 void unregister(int propertyId); 257 } 258 } 259 260