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 
17 package android.car.hardware.property;
18 
19 import static java.lang.Integer.toHexString;
20 
21 import android.annotation.Nullable;
22 import android.car.Car;
23 import android.car.CarNotConnectedException;
24 import android.car.hardware.CarPropertyConfig;
25 import android.car.hardware.CarPropertyValue;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.lang.ref.WeakReference;
36 import java.util.List;
37 
38 /**
39  * API for creating Car*Manager
40  * @hide
41  */
42 public class CarPropertyManagerBase {
43     private final boolean mDbg;
44     private final Handler mHandler;
45     private final ICarProperty mService;
46     private final String mTag;
47 
48     @GuardedBy("mLock")
49     private ICarPropertyEventListener mListenerToService;
50     @GuardedBy("mLock")
51     private CarPropertyEventCallback mCallback;
52 
53     private final Object mLock = new Object();
54 
55     /** Callback functions for property events */
56     public interface CarPropertyEventCallback {
57         /** Called when a property is updated */
onChangeEvent(CarPropertyValue value)58         void onChangeEvent(CarPropertyValue value);
59 
60         /** Called when an error is detected with a property */
onErrorEvent(int propertyId, int zone)61         void onErrorEvent(int propertyId, int zone);
62     }
63 
64     private final static class EventCallbackHandler extends Handler {
65         /** Constants handled in the handler */
66         private static final int MSG_GENERIC_EVENT = 0;
67 
68         private final WeakReference<CarPropertyManagerBase> mMgr;
69 
EventCallbackHandler(CarPropertyManagerBase mgr, Looper looper)70         EventCallbackHandler(CarPropertyManagerBase mgr, Looper looper) {
71             super(looper);
72             mMgr = new WeakReference<>(mgr);
73         }
74 
75         @Override
handleMessage(Message msg)76         public void handleMessage(Message msg) {
77             switch (msg.what) {
78                 case MSG_GENERIC_EVENT:
79                     CarPropertyManagerBase mgr = mMgr.get();
80                     if (mgr != null) {
81                         mgr.dispatchEventToClient((CarPropertyEvent) msg.obj);
82                     }
83                     break;
84                 default:
85                     Log.e("EventtCallbackHandler", "Event type not handled:  " + msg);
86                     break;
87             }
88         }
89     }
90 
91     /**
92      * Get an instance of the CarPropertyManagerBase.
93      */
CarPropertyManagerBase(IBinder service, Handler handler, boolean dbg, String tag)94     public CarPropertyManagerBase(IBinder service, Handler handler, boolean dbg,
95             String tag) {
96         mDbg = dbg;
97         mTag = tag;
98         mService = ICarProperty.Stub.asInterface(service);
99         mHandler = new EventCallbackHandler(this, handler.getLooper());
100     }
101 
registerCallback(CarPropertyEventCallback callback)102     public void registerCallback(CarPropertyEventCallback callback)
103             throws CarNotConnectedException {
104         synchronized (mLock) {
105             if (mCallback != null) {
106                 throw new IllegalStateException("Callback is already registered.");
107             }
108 
109             mCallback = callback;
110             mListenerToService = new ICarPropertyEventListener.Stub() {
111                 @Override
112                 public void onEvent(CarPropertyEvent event) throws RemoteException {
113                     handleEvent(event);
114                 }
115             };
116         }
117 
118         try {
119             mService.registerListener(mListenerToService);
120         } catch (RemoteException ex) {
121             Log.e(mTag, "Could not connect: ", ex);
122             throw new CarNotConnectedException(ex);
123         } catch (IllegalStateException ex) {
124             Car.checkCarNotConnectedExceptionFromCarService(ex);
125         }
126     }
127 
unregisterCallback()128     public void unregisterCallback() {
129         ICarPropertyEventListener listenerToService;
130         synchronized (mLock) {
131             listenerToService = mListenerToService;
132             mCallback = null;
133             mListenerToService = null;
134         }
135 
136         if (listenerToService == null) {
137             Log.w(mTag, "unregisterListener: listener was not registered");
138             return;
139         }
140 
141         try {
142             mService.unregisterListener(listenerToService);
143         } catch (RemoteException ex) {
144             Log.e(mTag, "Failed to unregister listener", ex);
145             //ignore
146         } catch (IllegalStateException ex) {
147             Car.hideCarNotConnectedExceptionFromCarService(ex);
148         }
149     }
150 
151     /**
152      * Returns the list of properties available.
153      *
154      * @return Caller must check the property type and typecast to the appropriate subclass
155      * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty)
156      */
getPropertyList()157     public List<CarPropertyConfig> getPropertyList()  throws CarNotConnectedException {
158         try {
159             return mService.getPropertyList();
160         } catch (RemoteException e) {
161             Log.e(mTag, "Exception in getPropertyList", e);
162             throw new CarNotConnectedException(e);
163         }
164     }
165 
166     /**
167      * Returns value of a bool property
168      *
169      * @param prop Property ID to get
170      * @param area Area of the property to get
171      */
getBooleanProperty(int prop, int area)172     public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException {
173         CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area);
174         return carProp != null ? carProp.getValue() : false;
175     }
176 
177     /**
178      * Returns value of a float property
179      *
180      * @param prop Property ID to get
181      * @param area Area of the property to get
182      */
getFloatProperty(int prop, int area)183     public float getFloatProperty(int prop, int area) throws CarNotConnectedException {
184         CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area);
185         return carProp != null ? carProp.getValue() : 0f;
186     }
187 
188     /**
189      * Returns value of a integer property
190      *
191      * @param prop Property ID to get
192      * @param area Zone of the property to get
193      */
getIntProperty(int prop, int area)194     public int getIntProperty(int prop, int area) throws CarNotConnectedException {
195         CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
196         return carProp != null ? carProp.getValue() : 0;
197     }
198 
199     @Nullable
200     @SuppressWarnings("unchecked")
getProperty(Class<E> clazz, int propId, int area)201     public <E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int area)
202             throws CarNotConnectedException {
203         if (mDbg) {
204             Log.d(mTag, "getProperty, propId: 0x" + toHexString(propId)
205                     + ", area: 0x" + toHexString(area) + ", class: " + clazz);
206         }
207         try {
208             CarPropertyValue<E> propVal = mService.getProperty(propId, area);
209             if (propVal != null && propVal.getValue() != null) {
210                 Class<?> actualClass = propVal.getValue().getClass();
211                 if (actualClass != clazz) {
212                     throw new IllegalArgumentException("Invalid property type. " + "Expected: "
213                             + clazz + ", but was: " + actualClass);
214                 }
215             }
216             return propVal;
217         } catch (RemoteException e) {
218             Log.e(mTag, "getProperty failed with " + e.toString()
219                     + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
220             throw new CarNotConnectedException(e);
221         }
222     }
223 
setProperty(Class<E> clazz, int propId, int area, E val)224     public <E> void setProperty(Class<E> clazz, int propId, int area, E val)
225             throws CarNotConnectedException {
226         if (mDbg) {
227             Log.d(mTag, "setProperty, propId: 0x" + toHexString(propId)
228                     + ", area: 0x" + toHexString(area) + ", class: " + clazz + ", val: " + val);
229         }
230         try {
231             mService.setProperty(new CarPropertyValue<>(propId, area, val));
232         } catch (RemoteException e) {
233             Log.e(mTag, "setProperty failed with " + e.toString(), e);
234             throw new CarNotConnectedException(e);
235         }
236     }
237 
238     /**
239      * Modifies a property.  If the property modification doesn't occur, an error event shall be
240      * generated and propagated back to the application.
241      *
242      * @param prop Property ID to modify
243      * @param area Area to apply the modification.
244      * @param val Value to set
245      */
setBooleanProperty(int prop, int area, boolean val)246     public void setBooleanProperty(int prop, int area, boolean val)
247             throws CarNotConnectedException {
248         setProperty(Boolean.class, prop, area, val);
249     }
250 
setFloatProperty(int prop, int area, float val)251     public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException {
252         setProperty(Float.class, prop, area, val);
253     }
254 
setIntProperty(int prop, int area, int val)255     public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException {
256         setProperty(Integer.class, prop, area, val);
257     }
258 
dispatchEventToClient(CarPropertyEvent event)259     private void dispatchEventToClient(CarPropertyEvent event) {
260         CarPropertyEventCallback listener;
261         synchronized (mLock) {
262             listener = mCallback;
263         }
264 
265         if (listener == null) {
266             Log.e(mTag, "Listener died, not dispatching event.");
267             return;
268         }
269 
270         CarPropertyValue propVal = event.getCarPropertyValue();
271         switch(event.getEventType()) {
272             case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE:
273                 listener.onChangeEvent(propVal);
274                 break;
275             case CarPropertyEvent.PROPERTY_EVENT_ERROR:
276                 listener.onErrorEvent(propVal.getPropertyId(), propVal.getAreaId());
277                 break;
278             default:
279                 throw new IllegalArgumentException();
280         }
281     }
282 
handleEvent(CarPropertyEvent event)283     private void handleEvent(CarPropertyEvent event) {
284         mHandler.sendMessage(mHandler.obtainMessage(EventCallbackHandler.MSG_GENERIC_EVENT, event));
285     }
286 
287     /** @hide */
onCarDisconnected()288     public void onCarDisconnected() {
289 
290         ICarPropertyEventListener listenerToService;
291         synchronized (mLock) {
292             listenerToService = mListenerToService;
293         }
294 
295         if (listenerToService != null) {
296             unregisterCallback();
297         }
298     }
299 }
300