1/*
2 * Copyright (C) 2016 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
17package com.android.car;
18
19import android.car.Car;
20import android.car.hardware.CarPropertyConfig;
21import android.car.hardware.CarPropertyValue;
22import android.car.hardware.property.CarPropertyEvent;
23import android.car.hardware.property.ICarProperty;
24import android.car.hardware.property.ICarPropertyEventListener;
25import android.content.Context;
26import android.os.IBinder;
27import android.os.RemoteException;
28import android.util.Log;
29
30import com.android.car.hal.PropertyHalServiceBase;
31
32import java.io.PrintWriter;
33import java.util.List;
34import java.util.Map;
35import java.util.concurrent.ConcurrentHashMap;
36
37/**
38 * This class implements the binder interface for ICarProperty.aidl to make it easier to create
39 * multiple managers that deal with Vehicle Properties. To create a new service, simply extend
40 * this class and call the super() constructor with the appropriate arguments for the new service.
41 * {@link CarHvacService} shows the basic usage.
42 */
43public class CarPropertyServiceBase extends ICarProperty.Stub
44        implements CarServiceBase, PropertyHalServiceBase.PropertyHalListener {
45    private final Context mContext;
46    private final boolean mDbg;
47    private final Map<IBinder, PropertyDeathRecipient> mDeathRecipientMap =
48            new ConcurrentHashMap<>();
49    private final PropertyHalServiceBase mHal;
50    private final Map<IBinder, ICarPropertyEventListener> mListenersMap = new ConcurrentHashMap<>();
51    private final String mPermission;
52    private final String mTag;
53
54    private final Object mLock = new Object();
55
56    public CarPropertyServiceBase(Context context, PropertyHalServiceBase hal, String permission,
57            boolean dbg, String tag) {
58        mContext = context;
59        mHal = hal;
60        mPermission = permission;
61        mDbg = dbg;
62        mTag = tag + ".service";
63    }
64
65    class PropertyDeathRecipient implements IBinder.DeathRecipient {
66        private IBinder mListenerBinder;
67
68        PropertyDeathRecipient(IBinder listenerBinder) {
69            mListenerBinder = listenerBinder;
70        }
71
72        /**
73         * Client died. Remove the listener from HAL service and unregister if this is the last
74         * client.
75         */
76        @Override
77        public void binderDied() {
78            if (mDbg) {
79                Log.d(mTag, "binderDied " + mListenerBinder);
80            }
81            CarPropertyServiceBase.this.unregisterListenerLocked(mListenerBinder);
82        }
83
84        void release() {
85            mListenerBinder.unlinkToDeath(this, 0);
86        }
87    }
88
89    @Override
90    public void init() {
91    }
92
93    @Override
94    public void release() {
95        for (PropertyDeathRecipient recipient : mDeathRecipientMap.values()) {
96            recipient.release();
97        }
98        mDeathRecipientMap.clear();
99        mListenersMap.clear();
100    }
101
102    @Override
103    public void dump(PrintWriter writer) {
104    }
105
106    @Override
107    public void registerListener(ICarPropertyEventListener listener) {
108        if (mDbg) {
109            Log.d(mTag, "registerListener");
110        }
111        ICarImpl.assertPermission(mContext, mPermission);
112        if (listener == null) {
113            Log.e(mTag, "registerListener: Listener is null.");
114            throw new IllegalArgumentException("listener cannot be null.");
115        }
116
117        IBinder listenerBinder = listener.asBinder();
118
119        synchronized (mLock) {
120            if (mListenersMap.containsKey(listenerBinder)) {
121                // Already registered, nothing to do.
122                return;
123            }
124
125            PropertyDeathRecipient deathRecipient = new PropertyDeathRecipient(listenerBinder);
126            try {
127                listenerBinder.linkToDeath(deathRecipient, 0);
128            } catch (RemoteException e) {
129                Log.e(mTag, "Failed to link death for recipient. " + e);
130                throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG);
131            }
132            mDeathRecipientMap.put(listenerBinder, deathRecipient);
133
134            if (mListenersMap.isEmpty()) {
135                mHal.setListener(this);
136            }
137
138            mListenersMap.put(listenerBinder, listener);
139        }
140    }
141
142    @Override
143    public void unregisterListener(ICarPropertyEventListener listener) {
144        if (mDbg) {
145            Log.d(mTag, "unregisterListener");
146        }
147        ICarImpl.assertPermission(mContext, mPermission);
148        if (listener == null) {
149            Log.e(mTag, "unregisterListener: Listener is null.");
150            throw new IllegalArgumentException("Listener is null");
151        }
152
153        IBinder listenerBinder = listener.asBinder();
154        synchronized (mLock) {
155            if (!mListenersMap.containsKey(listenerBinder)) {
156                Log.e(mTag, "unregisterListener: Listener was not previously registered.");
157            }
158            unregisterListenerLocked(listenerBinder);
159        }
160    }
161
162    // Removes the listenerBinder from the current state.
163    // The function assumes that binder will exist both in listeners and death recipients list.
164    private void unregisterListenerLocked(IBinder listenerBinder) {
165        boolean found = mListenersMap.remove(listenerBinder) != null;
166
167        if (found) {
168            mDeathRecipientMap.get(listenerBinder).release();
169            mDeathRecipientMap.remove(listenerBinder);
170        }
171
172        if (mListenersMap.isEmpty()) {
173            mHal.setListener(null);
174        }
175    }
176
177    @Override
178    public List<CarPropertyConfig> getPropertyList() {
179        ICarImpl.assertPermission(mContext, mPermission);
180        return mHal.getPropertyList();
181    }
182
183    @Override
184    public CarPropertyValue getProperty(int prop, int zone) {
185        ICarImpl.assertPermission(mContext, mPermission);
186        return mHal.getProperty(prop, zone);
187    }
188
189    @Override
190    public void setProperty(CarPropertyValue prop) {
191        ICarImpl.assertPermission(mContext, mPermission);
192        mHal.setProperty(prop);
193    }
194
195    private ICarPropertyEventListener[] getListeners() {
196        synchronized (mLock) {
197            int size = mListenersMap.values().size();
198            return mListenersMap.values().toArray(new ICarPropertyEventListener[size]);
199        }
200    }
201
202    // Implement PropertyHalListener interface
203    @Override
204    public void onPropertyChange(CarPropertyEvent event) {
205        for (ICarPropertyEventListener listener : getListeners()) {
206            try {
207                listener.onEvent(event);
208            } catch (RemoteException ex) {
209                // If we could not send a record, its likely the connection snapped. Let the binder
210                // death handle the situation.
211                Log.e(mTag, "onEvent calling failed: " + ex);
212            }
213        }
214    }
215
216    @Override
217    public void onPropertySetError(int property, int area) {
218        for (ICarPropertyEventListener listener : getListeners()) {
219            try {
220                listener.onEvent(createErrorEvent(property, area));
221            } catch (RemoteException ex) {
222                // If we could not send a record, its likely the connection snapped. Let the binder
223                // death handle the situation.
224                Log.e(mTag, "onEvent calling failed: " + ex);
225            }
226        }
227    }
228
229    private static CarPropertyEvent createErrorEvent(int property, int area) {
230        return new CarPropertyEvent(CarPropertyEvent.PROPERTY_EVENT_ERROR,
231                new CarPropertyValue<>(property, area, null));
232    }
233}
234