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