• 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         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