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