• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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