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.property;
18 
19 import android.car.builtin.util.Slogf;
20 import android.car.hardware.CarPropertyValue;
21 import android.car.hardware.property.CarPropertyEvent;
22 import android.car.hardware.property.ICarPropertyEventListener;
23 import android.os.IBinder;
24 import android.os.RemoteException;
25 import android.util.ArraySet;
26 import android.util.Log;
27 
28 import com.android.car.CarLog;
29 import com.android.car.CarPropertyService;
30 import com.android.car.internal.property.CarPropertyEventController;
31 import com.android.internal.annotations.GuardedBy;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /** Client class to keep track of listeners to {@link CarPropertyService}. */
37 public final class CarPropertyServiceClient extends CarPropertyEventController
38         implements IBinder.DeathRecipient {
39     private static final String TAG = CarLog.tagFor(CarPropertyServiceClient.class);
40     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
41 
42     private final ICarPropertyEventListener mListener;
43     private final IBinder mListenerBinder;
44     private final UnregisterCallback mUnregisterCallback;
45     private final Object mLock = new Object();
46     @GuardedBy("mLock")
47     private boolean mIsDead;
48 
CarPropertyServiceClient(ICarPropertyEventListener listener, UnregisterCallback unregisterCallback)49     public CarPropertyServiceClient(ICarPropertyEventListener listener,
50             UnregisterCallback unregisterCallback) {
51         super(/* useSystemLogger= */ true);
52         mListener = listener;
53         mListenerBinder = listener.asBinder();
54         mUnregisterCallback = unregisterCallback;
55 
56         try {
57             mListenerBinder.linkToDeath(this, /* flags= */ 0);
58         } catch (RemoteException e) {
59             Slogf.w(TAG, "IBinder=%s is already dead", mListenerBinder);
60             mIsDead = true;
61         }
62     }
63 
64     /**
65      * Returns whether this client is already dead.
66      *
67      * Caller should not assume this client is alive after getting True response because the
68      * binder might die after this function checks the status. Caller should only use this
69      * function to fail early.
70      */
isDead()71     public boolean isDead() {
72         synchronized (mLock) {
73             return mIsDead;
74         }
75     }
76 
77     /**
78      * Store a continuous property ID and area IDs with the update rate in hz that this
79      * client is subscribed at.
80      */
81     @Override
addContinuousProperty(int propertyId, int[] areaIds, float updateRateHz, boolean enableVur, float resolution)82     public void addContinuousProperty(int propertyId, int[] areaIds, float updateRateHz,
83             boolean enableVur, float resolution) {
84         synchronized (mLock) {
85             if (mIsDead) {
86                 Slogf.w(TAG, "addContinuousProperty: The client is already dead, ignore");
87                 return;
88             }
89 
90             super.addContinuousProperty(propertyId, areaIds, updateRateHz, enableVur, resolution);
91         }
92     }
93 
94     /**
95      * Stores a newly subscribed on-change property and area IDs.
96      */
97     @Override
addOnChangeProperty(int propertyId, int[] areaIds)98     public void addOnChangeProperty(int propertyId, int[] areaIds) {
99         synchronized (mLock) {
100             if (mIsDead) {
101                 Slogf.w(TAG, "addOnChangeProperty: The client is already dead, ignore");
102                 return;
103             }
104 
105             super.addOnChangeProperty(propertyId, areaIds);
106         }
107     }
108 
109     /**
110      * Remove property IDs that this client is subscribed to.
111      *
112      * Once all property IDs are removed, this client instance must be removed because
113      * {@link #mListenerBinder} will no longer be receiving death notifications.
114      *
115      * @return {@code true} if there are no properties registered to this client
116      */
117     @Override
remove(ArraySet<Integer> propertyIds)118     public boolean remove(ArraySet<Integer> propertyIds) {
119         synchronized (mLock) {
120             boolean empty = super.remove(propertyIds);
121             if (empty) {
122                 mListenerBinder.unlinkToDeath(this, /* flags= */ 0);
123             }
124             return empty;
125         }
126     }
127 
128     /**
129      * Handler to be called when client died.
130      *
131      * Remove the listener from HAL service and unregister if this is the last client.
132      */
133     @Override
binderDied()134     public void binderDied() {
135         List<Integer> propertyIds = new ArrayList<>();
136         synchronized (mLock) {
137             mIsDead = true;
138 
139             if (DBG) {
140                 Slogf.d(TAG, "binderDied %s", mListenerBinder);
141             }
142 
143             // Because we set mIsDead to true here, we are sure subscribeProperties will not
144             // have new elements. The property IDs here cover all the properties that we need to
145             // unregister.
146             for (int propertyId : getSubscribedProperties()) {
147                 propertyIds.add(propertyId);
148             }
149         }
150         try {
151             mUnregisterCallback.onUnregister(propertyIds, mListenerBinder);
152         } catch (Exception e) {
153             Slogf.e(TAG, "registerCallback.onUnregister failed", e);
154         }
155     }
156 
157     /**
158      * Calls onEvent function on the listener if the binder is alive.
159      *
160      * The property events will be filtered based on timestamp and whether VUR is on.
161      *
162      * There is still chance when onEvent might fail because binderDied is not called before
163      * this function.
164      */
onEvent(List<CarPropertyEvent> events)165     public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
166         List<CarPropertyEvent> filteredEvents = new ArrayList<>();
167         synchronized (mLock) {
168             if (mIsDead) {
169                 return;
170             }
171             for (int i = 0; i < events.size(); i++) {
172                 CarPropertyEvent event = events.get(i);
173                 CarPropertyValue<?> carPropertyValue = getCarPropertyValueIfCallbackRequired(event);
174                 if (carPropertyValue == null) {
175                     continue;
176                 }
177                 if (!carPropertyValue.equals(event.getCarPropertyValue())) {
178                     CarPropertyEvent updatedEvent =
179                             new CarPropertyEvent(event.getEventType(), carPropertyValue,
180                                     event.getErrorCode());
181                     filteredEvents.add(updatedEvent);
182                 } else {
183                     filteredEvents.add(event);
184                 }
185             }
186         }
187         onFilteredEvents(filteredEvents);
188     }
189 
190     /**
191      * Calls onEvent function on the listener if the binder is alive with no filtering.
192      *
193      * There is still chance when onEvent might fail because binderDied is not called before
194      * this function.
195      */
onFilteredEvents(List<CarPropertyEvent> events)196     public void onFilteredEvents(List<CarPropertyEvent> events) throws RemoteException {
197         if (!events.isEmpty()) {
198             mListener.onEvent(events);
199         }
200     }
201 
202     /**
203      * Interface that receives updates when unregistering properties with {@link
204      * CarPropertyService}.
205      */
206     public interface UnregisterCallback {
207 
208         /**
209          * Callback from {@link CarPropertyService} when unregistering properties.
210          *
211          * @param propertyIds the properties to be unregistered
212          * @param listenerBinder the client to be removed
213          */
onUnregister(List<Integer> propertyIds, IBinder listenerBinder)214         void onUnregister(List<Integer> propertyIds, IBinder listenerBinder);
215     }
216 }
217