Home | History | Annotate | Download | only in property
      1 /*
      2  * Copyright (C) 2018 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.car.CarApiUtil;
     22 import android.car.CarManagerBase;
     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.RemoteException;
     29 import android.util.ArraySet;
     30 import android.util.Log;
     31 import android.util.SparseArray;
     32 
     33 import com.android.car.internal.CarRatedFloatListeners;
     34 import com.android.car.internal.SingleMessageHandler;
     35 
     36 import java.lang.ref.WeakReference;
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 import java.util.function.Consumer;
     40 
     41 
     42 /**
     43  * API for creating Car*Manager
     44  * @hide
     45  */
     46 public class CarPropertyManager implements CarManagerBase {
     47     private final boolean mDbg;
     48     private final SingleMessageHandler<CarPropertyEvent> mHandler;
     49     private final ICarProperty mService;
     50     private final String mTag;
     51     private static final int MSG_GENERIC_EVENT = 0;
     52 
     53     private CarPropertyEventListenerToService mCarPropertyEventToService;
     54 
     55 
     56     /** Record of locally active properties. Key is propertyId */
     57     private final SparseArray<CarPropertyListeners> mActivePropertyListener =
     58             new SparseArray<>();
     59 
     60     /** Callback functions for property events */
     61     public interface CarPropertyEventListener {
     62         /** Called when a property is updated */
     63         void onChangeEvent(CarPropertyValue value);
     64 
     65         /** Called when an error is detected with a property */
     66         void onErrorEvent(int propId, int zone);
     67     }
     68 
     69     /**
     70      * Get an instance of the CarPropertyManager.
     71      */
     72     public CarPropertyManager(IBinder service, Handler handler, boolean dbg, String tag) {
     73         mDbg = dbg;
     74         mTag = tag;
     75         mService = ICarProperty.Stub.asInterface(service);
     76         mHandler = new SingleMessageHandler<CarPropertyEvent>(handler.getLooper(),
     77                 MSG_GENERIC_EVENT) {
     78             @Override
     79             protected void handleEvent(CarPropertyEvent event) {
     80                 CarPropertyListeners listeners;
     81                 synchronized (mActivePropertyListener) {
     82                     listeners = mActivePropertyListener.get(
     83                             event.getCarPropertyValue().getPropertyId());
     84                 }
     85                 if (listeners != null) {
     86                     switch (event.getEventType()) {
     87                         case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE:
     88                             listeners.onPropertyChanged(event);
     89                             break;
     90                         case CarPropertyEvent.PROPERTY_EVENT_ERROR:
     91                             listeners.onErrorEvent(event);
     92                             break;
     93                         default:
     94                             throw new IllegalArgumentException();
     95                     }
     96                 }
     97             }
     98         };
     99     }
    100 
    101     /** Use to register or update Callback for properties */
    102     public boolean registerListener(CarPropertyEventListener listener, int propertyId, float rate)
    103             throws CarNotConnectedException {
    104         synchronized (mActivePropertyListener) {
    105             if (mCarPropertyEventToService == null) {
    106                 mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
    107             }
    108             boolean needsServerUpdate = false;
    109             CarPropertyListeners listeners;
    110             listeners = mActivePropertyListener.get(propertyId);
    111             if (listeners == null) {
    112                 listeners = new CarPropertyListeners(rate);
    113                 mActivePropertyListener.put(propertyId, listeners);
    114                 needsServerUpdate = true;
    115             }
    116             if (listeners.addAndUpdateRate(listener, rate)) {
    117                 needsServerUpdate = true;
    118             }
    119             if (needsServerUpdate) {
    120                 if (!registerOrUpdatePropertyListener(propertyId, rate)) {
    121                     return false;
    122                 }
    123             }
    124         }
    125         return true;
    126     }
    127 
    128     private boolean registerOrUpdatePropertyListener(int propertyId, float rate)
    129             throws CarNotConnectedException {
    130         try {
    131             mService.registerListener(propertyId, rate, mCarPropertyEventToService);
    132         } catch (IllegalStateException e) {
    133             CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
    134         } catch (RemoteException e) {
    135             throw new CarNotConnectedException(e);
    136         }
    137         return true;
    138     }
    139 
    140     private class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub{
    141         private final WeakReference<CarPropertyManager> mMgr;
    142 
    143         CarPropertyEventListenerToService(CarPropertyManager mgr) {
    144             mMgr = new WeakReference<>(mgr);
    145         }
    146 
    147         @Override
    148         public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
    149             CarPropertyManager manager = mMgr.get();
    150             if (manager != null) {
    151                 manager.handleEvent(events);
    152             }
    153         }
    154     }
    155 
    156     private void handleEvent(List<CarPropertyEvent> events) {
    157         mHandler.sendEvents(events);
    158     }
    159 
    160     /**
    161      * Stop getting sensor update for the given listener. If there are multiple registrations for
    162      * this listener, all listening will be stopped.
    163      * @param listener
    164      */
    165     public void unregisterListener(CarPropertyEventListener listener) {
    166         synchronized (mActivePropertyListener) {
    167             for (int i = 0; i < mActivePropertyListener.size(); i++) {
    168                 doUnregisterListenerLocked(listener, mActivePropertyListener.keyAt(i));
    169             }
    170         }
    171     }
    172 
    173     /**
    174      * Stop getting sensor update for the given listener and sensor. If the same listener is used
    175      * for other sensors, those subscriptions will not be affected.
    176      * @param listener
    177      * @param propertyId
    178      */
    179     public void unregisterListener(CarPropertyEventListener listener, int propertyId) {
    180         synchronized (mActivePropertyListener) {
    181             doUnregisterListenerLocked(listener, propertyId);
    182         }
    183     }
    184 
    185     private void doUnregisterListenerLocked(CarPropertyEventListener listener, int propertyId) {
    186         CarPropertyListeners listeners = mActivePropertyListener.get(propertyId);
    187         if (listeners != null) {
    188             boolean needsServerUpdate = false;
    189             if (listeners.contains(listener)) {
    190                 needsServerUpdate = listeners.remove(listener);
    191             }
    192             if (listeners.isEmpty()) {
    193                 try {
    194                     mService.unregisterListener(propertyId, mCarPropertyEventToService);
    195                 } catch (RemoteException e) {
    196                     //ignore
    197                 }
    198                 mActivePropertyListener.remove(propertyId);
    199             } else if (needsServerUpdate) {
    200                 try {
    201                     registerOrUpdatePropertyListener(propertyId, listeners.getRate());
    202                 } catch (CarNotConnectedException e) {
    203                     // ignore
    204                 }
    205             }
    206         }
    207     }
    208 
    209     /**
    210      * Returns the list of properties implemented by this car.
    211      *
    212      * @return Caller must check the property type and typecast to the appropriate subclass
    213      * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty)
    214      */
    215     public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException {
    216         try {
    217             return mService.getPropertyList();
    218         } catch (RemoteException e) {
    219             Log.e(mTag, "getPropertyList exception ", e);
    220             throw new CarNotConnectedException(e);
    221         }
    222     }
    223 
    224     /**
    225      * Returns the list of properties implemented by this car in given property id list.
    226      *
    227      * @return Caller must check the property type and typecast to the appropriate subclass
    228      * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty)
    229      */
    230     public List<CarPropertyConfig> getPropertyList(ArraySet<Integer> propertyIds)
    231             throws CarNotConnectedException {
    232         try {
    233             List<CarPropertyConfig> configs = new ArrayList<>();
    234             for (CarPropertyConfig c : mService.getPropertyList()) {
    235                 if (propertyIds.contains(c.getPropertyId())) {
    236                     configs.add(c);
    237                 }
    238             }
    239             return configs;
    240         } catch (RemoteException e) {
    241             Log.e(mTag, "getPropertyList exception ", e);
    242             throw new CarNotConnectedException(e);
    243         }
    244 
    245     }
    246 
    247     /**
    248      * Check whether a given property is available or disabled based on the car's current state.
    249      * @return true if STATUS_AVAILABLE, false otherwise (eg STATUS_UNAVAILABLE)
    250      * @throws CarNotConnectedException
    251      */
    252     public boolean isPropertyAvailable(int propId, int area) throws CarNotConnectedException {
    253         try {
    254             CarPropertyValue propValue = mService.getProperty(propId, area);
    255             return (propValue != null)
    256                     && (propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE);
    257         } catch (RemoteException e) {
    258             Log.e(mTag, "isPropertyAvailable failed with " + e.toString()
    259                     + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
    260             throw new CarNotConnectedException(e);
    261         }
    262     }
    263 
    264     /**
    265      * Returns value of a bool property
    266      *
    267      * @param prop Property ID to get
    268      * @param area Area of the property to get
    269      */
    270     public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException {
    271         CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area);
    272         return carProp != null ? carProp.getValue() : false;
    273     }
    274 
    275     /**
    276      * Returns value of a float property
    277      *
    278      * @param prop Property ID to get
    279      * @param area Area of the property to get
    280      */
    281     public float getFloatProperty(int prop, int area) throws CarNotConnectedException {
    282         CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area);
    283         return carProp != null ? carProp.getValue() : 0f;
    284     }
    285 
    286     /**
    287      * Returns value of a integer property
    288      *
    289      * @param prop Property ID to get
    290      * @param area Zone of the property to get
    291      */
    292     public int getIntProperty(int prop, int area) throws CarNotConnectedException {
    293         CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
    294         return carProp != null ? carProp.getValue() : 0;
    295     }
    296 
    297     /** Return CarPropertyValue */
    298     @SuppressWarnings("unchecked")
    299     public <E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int area)
    300             throws CarNotConnectedException {
    301         if (mDbg) {
    302             Log.d(mTag, "getProperty, propId: 0x" + toHexString(propId)
    303                     + ", area: 0x" + toHexString(area) + ", class: " + clazz);
    304         }
    305         try {
    306             CarPropertyValue<E> propVal = mService.getProperty(propId, area);
    307             if (propVal != null && propVal.getValue() != null) {
    308                 Class<?> actualClass = propVal.getValue().getClass();
    309                 if (actualClass != clazz) {
    310                     throw new IllegalArgumentException("Invalid property type. " + "Expected: "
    311                             + clazz + ", but was: " + actualClass);
    312                 }
    313             }
    314             return propVal;
    315         } catch (RemoteException e) {
    316             Log.e(mTag, "getProperty failed with " + e.toString()
    317                     + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
    318             throw new CarNotConnectedException(e);
    319         }
    320     }
    321 
    322     /** Return raw CarPropertyValue */
    323     public <E> CarPropertyValue<E> getProperty(int propId, int area)
    324             throws CarNotConnectedException {
    325         try {
    326             CarPropertyValue<E> propVal = mService.getProperty(propId, area);
    327             return propVal;
    328         } catch (RemoteException e) {
    329             Log.e(mTag, "getProperty failed with " + e.toString()
    330                     + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
    331             throw new CarNotConnectedException(e);
    332         }
    333     }
    334 
    335     /** Set CarPropertyValue */
    336     public <E> void setProperty(Class<E> clazz, int propId, int area, E val)
    337             throws CarNotConnectedException {
    338         if (mDbg) {
    339             Log.d(mTag, "setProperty, propId: 0x" + toHexString(propId)
    340                     + ", area: 0x" + toHexString(area) + ", class: " + clazz + ", val: " + val);
    341         }
    342         try {
    343             mService.setProperty(new CarPropertyValue<>(propId, area, val));
    344         } catch (RemoteException e) {
    345             Log.e(mTag, "setProperty failed with " + e.toString(), e);
    346             throw new CarNotConnectedException(e);
    347         }
    348     }
    349 
    350     /**
    351      * Modifies a property.  If the property modification doesn't occur, an error event shall be
    352      * generated and propagated back to the application.
    353      *
    354      * @param prop Property ID to modify
    355      * @param area Area to apply the modification.
    356      * @param val Value to set
    357      */
    358     public void setBooleanProperty(int prop, int area, boolean val)
    359             throws CarNotConnectedException {
    360         setProperty(Boolean.class, prop, area, val);
    361     }
    362 
    363     /** Set float value of property*/
    364     public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException {
    365         setProperty(Float.class, prop, area, val);
    366     }
    367     /** Set int value of property*/
    368     public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException {
    369         setProperty(Integer.class, prop, area, val);
    370     }
    371 
    372 
    373     private class CarPropertyListeners extends CarRatedFloatListeners<CarPropertyEventListener> {
    374         CarPropertyListeners(float rate) {
    375             super(rate);
    376         }
    377         void onPropertyChanged(final CarPropertyEvent event) {
    378             // throw away old sensor data as oneway binder call can change order.
    379             long updateTime = event.getCarPropertyValue().getTimestamp();
    380             if (updateTime < mLastUpdateTime) {
    381                 Log.w(mTag, "dropping old property data");
    382                 return;
    383             }
    384             mLastUpdateTime = updateTime;
    385             List<CarPropertyEventListener> listeners;
    386             synchronized (mActivePropertyListener) {
    387                 listeners = new ArrayList<>(getListeners());
    388             }
    389             listeners.forEach(new Consumer<CarPropertyEventListener>() {
    390                 @Override
    391                 public void accept(CarPropertyEventListener listener) {
    392                     listener.onChangeEvent(event.getCarPropertyValue());
    393                 }
    394             });
    395         }
    396 
    397         void onErrorEvent(final CarPropertyEvent event) {
    398             List<CarPropertyEventListener> listeners;
    399             CarPropertyValue value = event.getCarPropertyValue();
    400             synchronized (mActivePropertyListener) {
    401                 listeners = new ArrayList<>(getListeners());
    402             }
    403             listeners.forEach(new Consumer<CarPropertyEventListener>() {
    404                 @Override
    405                 public void accept(CarPropertyEventListener listener) {
    406                     listener.onErrorEvent(value.getPropertyId(), value.getAreaId());
    407                 }
    408             });
    409         }
    410     }
    411 
    412     /** @hide */
    413     @Override
    414     public void onCarDisconnected() {
    415         synchronized (mActivePropertyListener) {
    416             mActivePropertyListener.clear();
    417             mCarPropertyEventToService = null;
    418         }
    419     }
    420 }
    421