1 /* 2 * Copyright (C) 2023 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.property; 18 19 import static com.android.car.internal.util.DebugUtils.toAreaIdString; 20 21 import static java.util.Objects.requireNonNull; 22 23 import android.car.VehiclePropertyIds; 24 import android.car.builtin.util.Slogf; 25 import android.car.hardware.CarPropertyValue; 26 import android.car.hardware.property.CarPropertyEvent; 27 import android.util.ArraySet; 28 import android.util.Log; 29 30 import com.android.car.internal.util.PairSparseArray; 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.annotations.VisibleForTesting; 33 34 /** 35 * Manages a group of property IDs and area IDs registered for the same client at possibly 36 * different update rates. The client here might be different callbacks at 37 * {@code CarPropertyManager} or might be different managers at {@code CarPropertyService}. 38 * 39 * This class is used to decide whether a new property update event needs to be filtered out and 40 * not passed to the client. 41 * 42 * This class is thread-safe. 43 */ 44 public class CarPropertyEventController { 45 // Abbreviating TAG because class name is longer than the 23 character Log tag limit. 46 private static final String TAG = "CPEController"; 47 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 48 private final Object mLock = new Object(); 49 // For each property ID and area ID, track the property event information. 50 @GuardedBy("mLock") 51 private final PairSparseArray<CarPropertyEventTracker> mPropIdToAreaIdToCpeTracker = 52 new PairSparseArray<>(); 53 CarPropertyEventController()54 public CarPropertyEventController() { 55 } 56 57 /** Gets the update rate in Hz for the property ID, area ID. */ 58 @VisibleForTesting getUpdateRateHz(int propertyId, int areaId)59 public float getUpdateRateHz(int propertyId, int areaId) { 60 synchronized (mLock) { 61 CarPropertyEventTracker tracker = mPropIdToAreaIdToCpeTracker.get(propertyId, areaId); 62 if (tracker == null) { 63 return 0f; 64 } 65 return tracker.getUpdateRateHz(); 66 } 67 } 68 69 /** Tracks the continuous property ID and area IDs at the given update rate. */ addContinuousProperty(int propertyId, int[] areaIds, float updateRateHz, boolean enableVur, float resolution)70 public void addContinuousProperty(int propertyId, int[] areaIds, float updateRateHz, 71 boolean enableVur, float resolution) { 72 requireNonNull(areaIds); 73 synchronized (mLock) { 74 for (int areaId : areaIds) { 75 if (DBG) { 76 Slogf.d(TAG, 77 "Add new continuous property event tracker, property: %s, " 78 + "areaId: %s, updateRate: %f Hz, enableVur: %b, resolution: " 79 + "%f", 80 VehiclePropertyIds.toString(propertyId), 81 toAreaIdString(propertyId, areaId), updateRateHz, 82 enableVur, resolution); 83 } 84 mPropIdToAreaIdToCpeTracker.put(propertyId, areaId, 85 new ContCarPropertyEventTracker(updateRateHz, enableVur, resolution)); 86 } 87 } 88 } 89 90 /** Tracks a newly subscribed on-change property ID and area IDs. */ addOnChangeProperty(int propertyId, int[] areaIds)91 public void addOnChangeProperty(int propertyId, int[] areaIds) { 92 requireNonNull(areaIds); 93 synchronized (mLock) { 94 for (int areaId : areaIds) { 95 if (DBG) { 96 Slogf.d(TAG, 97 "Add new on-change property event tracker, property: %s, " 98 + "areaId: %s", VehiclePropertyIds.toString(propertyId), 99 toAreaIdString(propertyId, areaId)); 100 } 101 mPropIdToAreaIdToCpeTracker.put(propertyId, areaId, 102 new OnChangeCarPropertyEventTracker()); 103 } 104 } 105 } 106 107 /** 108 * Returns the areaIds associated with the given propertyId 109 */ getAreaIds(int propertyId)110 public int[] getAreaIds(int propertyId) { 111 synchronized (mLock) { 112 return setToArray(mPropIdToAreaIdToCpeTracker.getSecondKeysForFirstKey(propertyId)); 113 } 114 } 115 116 /** 117 * Stop tracking the given property ID. 118 * 119 * @return {@code true} if there are no remaining properties being tracked. 120 */ remove(int propertyId)121 public boolean remove(int propertyId) { 122 synchronized (mLock) { 123 for (int areaId : mPropIdToAreaIdToCpeTracker.getSecondKeysForFirstKey(propertyId)) { 124 mPropIdToAreaIdToCpeTracker.delete(propertyId, areaId); 125 } 126 return mPropIdToAreaIdToCpeTracker.size() == 0; 127 } 128 } 129 130 /** 131 * Stop tracking the given property IDs. 132 * 133 * @return {@code true} if there are no remaining properties being tracked. 134 */ remove(ArraySet<Integer> propertyIds)135 public boolean remove(ArraySet<Integer> propertyIds) { 136 synchronized (mLock) { 137 for (int i = 0; i < propertyIds.size(); i++) { 138 int propertyId = propertyIds.valueAt(i); 139 for (int areaId : 140 mPropIdToAreaIdToCpeTracker.getSecondKeysForFirstKey(propertyId)) { 141 mPropIdToAreaIdToCpeTracker.delete(propertyId, areaId); 142 } 143 } 144 return mPropIdToAreaIdToCpeTracker.size() == 0; 145 } 146 } 147 148 /** Returns a list of property IDs being tracked for the client. */ getSubscribedProperties()149 public int[] getSubscribedProperties() { 150 synchronized (mLock) { 151 return setToArray(mPropIdToAreaIdToCpeTracker.getFirstKeys()); 152 } 153 } 154 155 /** 156 * Returns a new sanitized CarPropertyValue if the client callback should be invoked for the 157 * event. 158 */ getCarPropertyValueIfCallbackRequired( CarPropertyEvent carPropertyEvent)159 protected CarPropertyValue<?> getCarPropertyValueIfCallbackRequired( 160 CarPropertyEvent carPropertyEvent) { 161 CarPropertyValue<?> carPropertyValue = carPropertyEvent.getCarPropertyValue(); 162 int propertyId = carPropertyValue.getPropertyId(); 163 int areaId = carPropertyValue.getAreaId(); 164 165 synchronized (mLock) { 166 CarPropertyEventTracker tracker = mPropIdToAreaIdToCpeTracker.get(propertyId, areaId); 167 if (tracker == null) { 168 Slogf.w(TAG, 169 "getCarPropertyValueIfCallbackRequired: callback not registered for event:" 170 + " %s", 171 carPropertyEvent); 172 return null; 173 } 174 if (carPropertyEvent.getEventType() == CarPropertyEvent.PROPERTY_EVENT_ERROR) { 175 return carPropertyValue; 176 } 177 if (tracker.hasUpdate(carPropertyValue)) { 178 return tracker.getCurrentCarPropertyValue(); 179 } 180 return null; 181 } 182 } 183 setToArray(ArraySet<Integer> ids)184 private static int[] setToArray(ArraySet<Integer> ids) { 185 int[] propertyIds = new int[ids.size()]; 186 for (int i = 0; i < ids.size(); i++) { 187 propertyIds[i] = ids.valueAt(i); 188 } 189 return propertyIds; 190 } 191 } 192