Home | History | Annotate | Download | only in car
      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 com.android.car;
     18 
     19 import android.annotation.Nullable;
     20 import android.car.drivingstate.CarDrivingStateEvent;
     21 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
     22 import android.car.drivingstate.CarUxRestrictions;
     23 import android.car.drivingstate.ICarDrivingStateChangeListener;
     24 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
     25 import android.car.drivingstate.ICarUxRestrictionsManager;
     26 import android.car.hardware.CarPropertyValue;
     27 import android.car.hardware.property.CarPropertyEvent;
     28 import android.car.hardware.property.ICarPropertyEventListener;
     29 import android.content.Context;
     30 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
     31 import android.os.IBinder;
     32 import android.os.RemoteException;
     33 import android.util.Log;
     34 
     35 import org.xmlpull.v1.XmlPullParserException;
     36 
     37 import java.io.IOException;
     38 import java.io.PrintWriter;
     39 import java.util.ArrayList;
     40 import java.util.LinkedList;
     41 import java.util.List;
     42 
     43 /**
     44  * A service that listens to current driving state of the vehicle and maps it to the
     45  * appropriate UX restrictions for that driving state.
     46  */
     47 public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements
     48         CarServiceBase {
     49     private static final String TAG = "CarUxR";
     50     private static final boolean DBG = false;
     51     private static final int MAX_TRANSITION_LOG_SIZE = 20;
     52     private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
     53     private static final float SPEED_NOT_AVAILABLE = -1.0F;
     54     private final Context mContext;
     55     private final CarDrivingStateService mDrivingStateService;
     56     private final CarPropertyService mCarPropertyService;
     57     private final CarUxRestrictionsServiceHelper mHelper;
     58     // List of clients listening to UX restriction events.
     59     private final List<UxRestrictionsClient> mUxRClients = new ArrayList<>();
     60     private CarUxRestrictions mCurrentUxRestrictions;
     61     private float mCurrentMovingSpeed;
     62     private boolean mFallbackToDefaults;
     63     // For dumpsys logging
     64     private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>();
     65 
     66 
     67     public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService,
     68             CarPropertyService propertyService) {
     69         mContext = context;
     70         mDrivingStateService = drvService;
     71         mCarPropertyService = propertyService;
     72         mHelper = new CarUxRestrictionsServiceHelper(mContext, R.xml.car_ux_restrictions_map);
     73         // Unrestricted until driving state information is received. During boot up, we don't want
     74         // everything to be blocked until data is available from CarPropertyManager.  If we start
     75         // driving and we don't get speed or gear information, we have bigger problems.
     76         mCurrentUxRestrictions = mHelper.createUxRestrictionsEvent(false,
     77                 CarUxRestrictions.UX_RESTRICTIONS_BASELINE);
     78     }
     79 
     80     @Override
     81     public synchronized void init() {
     82         try {
     83             if (!mHelper.loadUxRestrictionsFromXml()) {
     84                 Log.e(TAG, "Error reading Ux Restrictions Mapping. Falling back to defaults");
     85                 mFallbackToDefaults = true;
     86             }
     87         } catch (IOException | XmlPullParserException e) {
     88             Log.e(TAG, "Exception reading UX restrictions XML mapping", e);
     89             mFallbackToDefaults = true;
     90         }
     91         // subscribe to driving State
     92         mDrivingStateService.registerDrivingStateChangeListener(
     93                 mICarDrivingStateChangeEventListener);
     94         // subscribe to property service for speed
     95         mCarPropertyService.registerListener(VehicleProperty.PERF_VEHICLE_SPEED,
     96                 PROPERTY_UPDATE_RATE, mICarPropertyEventListener);
     97         initializeUxRestrictions();
     98     }
     99 
    100     // Update current restrictions by getting the current driving state and speed.
    101     private void initializeUxRestrictions() {
    102         CarDrivingStateEvent currentDrivingStateEvent =
    103                 mDrivingStateService.getCurrentDrivingState();
    104         // if we don't have enough information from the CarPropertyService to compute the UX
    105         // restrictions, then leave the UX restrictions unchanged from what it was initialized to
    106         // in the constructor.
    107         if (currentDrivingStateEvent == null || currentDrivingStateEvent.eventValue
    108                 == CarDrivingStateEvent.DRIVING_STATE_UNKNOWN) {
    109             return;
    110         }
    111         int currentDrivingState = currentDrivingStateEvent.eventValue;
    112         Float currentSpeed = getCurrentSpeed();
    113         if (currentSpeed == SPEED_NOT_AVAILABLE) {
    114             return;
    115         }
    116         // At this point the underlying CarPropertyService has provided us enough information to
    117         // compute the UX restrictions that could be potentially different from the initial UX
    118         // restrictions.
    119         handleDispatchUxRestrictions(currentDrivingState, currentSpeed);
    120     }
    121 
    122     private Float getCurrentSpeed() {
    123         CarPropertyValue value = mCarPropertyService.getProperty(VehicleProperty.PERF_VEHICLE_SPEED,
    124                 0);
    125         if (value != null) {
    126             return (Float) value.getValue();
    127         }
    128         return SPEED_NOT_AVAILABLE;
    129     }
    130 
    131     @Override
    132     public synchronized void release() {
    133         for (UxRestrictionsClient client : mUxRClients) {
    134             client.listenerBinder.unlinkToDeath(client, 0);
    135         }
    136         mUxRClients.clear();
    137         mDrivingStateService.unregisterDrivingStateChangeListener(
    138                 mICarDrivingStateChangeEventListener);
    139     }
    140 
    141     // Binder methods
    142 
    143     /**
    144      * Register a {@link ICarUxRestrictionsChangeListener} to be notified for changes to the UX
    145      * restrictions
    146      *
    147      * @param listener listener to register
    148      */
    149     @Override
    150     public synchronized void registerUxRestrictionsChangeListener(
    151             ICarUxRestrictionsChangeListener listener) {
    152         if (listener == null) {
    153             if (DBG) {
    154                 Log.e(TAG, "registerUxRestrictionsChangeListener(): listener null");
    155             }
    156             throw new IllegalArgumentException("Listener is null");
    157         }
    158         // If a new client is registering, create a new DrivingStateClient and add it to the list
    159         // of listening clients.
    160         UxRestrictionsClient client = findUxRestrictionsClient(listener);
    161         if (client == null) {
    162             client = new UxRestrictionsClient(listener);
    163             try {
    164                 listener.asBinder().linkToDeath(client, 0);
    165             } catch (RemoteException e) {
    166                 Log.e(TAG, "Cannot link death recipient to binder " + e);
    167             }
    168             mUxRClients.add(client);
    169         }
    170         return;
    171     }
    172 
    173     /**
    174      * Iterates through the list of registered UX Restrictions clients -
    175      * {@link UxRestrictionsClient} and finds if the given client is already registered.
    176      *
    177      * @param listener Listener to look for.
    178      * @return the {@link UxRestrictionsClient} if found, null if not
    179      */
    180     @Nullable
    181     private UxRestrictionsClient findUxRestrictionsClient(
    182             ICarUxRestrictionsChangeListener listener) {
    183         IBinder binder = listener.asBinder();
    184         for (UxRestrictionsClient client : mUxRClients) {
    185             if (client.isHoldingBinder(binder)) {
    186                 return client;
    187             }
    188         }
    189         return null;
    190     }
    191 
    192     /**
    193      * Unregister the given UX Restrictions listener
    194      *
    195      * @param listener client to unregister
    196      */
    197     @Override
    198     public synchronized void unregisterUxRestrictionsChangeListener(
    199             ICarUxRestrictionsChangeListener listener) {
    200         if (listener == null) {
    201             Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null");
    202             throw new IllegalArgumentException("Listener is null");
    203         }
    204 
    205         UxRestrictionsClient client = findUxRestrictionsClient(listener);
    206         if (client == null) {
    207             Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener was not previously "
    208                     + "registered");
    209             return;
    210         }
    211         listener.asBinder().unlinkToDeath(client, 0);
    212         mUxRClients.remove(client);
    213     }
    214 
    215     /**
    216      * Gets the current UX restrictions
    217      *
    218      * @return {@link CarUxRestrictions} for the given event type
    219      */
    220     @Override
    221     @Nullable
    222     public synchronized CarUxRestrictions getCurrentUxRestrictions() {
    223         return mCurrentUxRestrictions;
    224     }
    225 
    226     /**
    227      * Class that holds onto client related information - listener interface, process that hosts the
    228      * binder object etc.
    229      * It also registers for death notifications of the host.
    230      */
    231     private class UxRestrictionsClient implements IBinder.DeathRecipient {
    232         private final IBinder listenerBinder;
    233         private final ICarUxRestrictionsChangeListener listener;
    234 
    235         public UxRestrictionsClient(ICarUxRestrictionsChangeListener l) {
    236             listener = l;
    237             listenerBinder = l.asBinder();
    238         }
    239 
    240         @Override
    241         public void binderDied() {
    242             if (DBG) {
    243                 Log.d(TAG, "Binder died " + listenerBinder);
    244             }
    245             listenerBinder.unlinkToDeath(this, 0);
    246             synchronized (CarUxRestrictionsManagerService.this) {
    247                 mUxRClients.remove(this);
    248             }
    249         }
    250 
    251         /**
    252          * Returns if the given binder object matches to what this client info holds.
    253          * Used to check if the listener asking to be registered is already registered.
    254          *
    255          * @return true if matches, false if not
    256          */
    257         public boolean isHoldingBinder(IBinder binder) {
    258             return listenerBinder == binder;
    259         }
    260 
    261         /**
    262          * Dispatch the event to the listener
    263          *
    264          * @param event {@link CarUxRestrictions}.
    265          */
    266         public void dispatchEventToClients(CarUxRestrictions event) {
    267             if (event == null) {
    268                 return;
    269             }
    270             try {
    271                 listener.onUxRestrictionsChanged(event);
    272             } catch (RemoteException e) {
    273                 if (DBG) {
    274                     Log.d(TAG, "Dispatch to listener failed");
    275                 }
    276             }
    277         }
    278     }
    279 
    280     @Override
    281     public void dump(PrintWriter writer) {
    282         writer.println(
    283                 "Requires DO? " + mCurrentUxRestrictions.isRequiresDistractionOptimization());
    284         writer.println("Current UXR: " + mCurrentUxRestrictions.getActiveRestrictions());
    285         mHelper.dump(writer);
    286         writer.println("UX Restriction change log:");
    287         for (Utils.TransitionLog tlog : mTransitionLogs) {
    288             writer.println(tlog);
    289         }
    290     }
    291 
    292     /**
    293      * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService}
    294      * for getting driving state change notifications.
    295      */
    296     private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
    297             new ICarDrivingStateChangeListener.Stub() {
    298                 @Override
    299                 public void onDrivingStateChanged(CarDrivingStateEvent event) {
    300                     if (DBG) {
    301                         Log.d(TAG, "Driving State Changed:" + event.eventValue);
    302                     }
    303                     handleDrivingStateEvent(event);
    304                 }
    305             };
    306 
    307     /**
    308      * Handle the driving state change events coming from the {@link CarDrivingStateService}.
    309      * Map the driving state to the corresponding UX Restrictions and dispatch the
    310      * UX Restriction change to the registered clients.
    311      */
    312     private synchronized void handleDrivingStateEvent(CarDrivingStateEvent event) {
    313         if (event == null) {
    314             return;
    315         }
    316         int drivingState = event.eventValue;
    317         Float speed = getCurrentSpeed();
    318 
    319         if (speed != SPEED_NOT_AVAILABLE) {
    320             mCurrentMovingSpeed = speed;
    321         } else if (drivingState == CarDrivingStateEvent.DRIVING_STATE_PARKED
    322                 || drivingState == CarDrivingStateEvent.DRIVING_STATE_UNKNOWN) {
    323             // If speed is unavailable, but the driving state is parked or unknown, it can still be
    324             // handled.
    325             if (DBG) {
    326                 Log.d(TAG, "Speed null when driving state is: " + drivingState);
    327             }
    328             mCurrentMovingSpeed = 0;
    329         } else {
    330             // If we get here with driving state != parked or unknown && speed == null,
    331             // something is wrong.  CarDrivingStateService could not have inferred idling or moving
    332             // when speed is not available
    333             Log.e(TAG, "Unexpected:  Speed null when driving state is: " + drivingState);
    334             return;
    335         }
    336         handleDispatchUxRestrictions(drivingState, mCurrentMovingSpeed);
    337     }
    338 
    339     /**
    340      * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
    341      * speed change notifications.
    342      */
    343     private final ICarPropertyEventListener mICarPropertyEventListener =
    344             new ICarPropertyEventListener.Stub() {
    345                 @Override
    346                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
    347                     for (CarPropertyEvent event : events) {
    348                         if ((event.getEventType()
    349                                 == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)
    350                                 && (event.getCarPropertyValue().getPropertyId()
    351                                 == VehicleProperty.PERF_VEHICLE_SPEED)) {
    352                             handleSpeedChange((Float) event.getCarPropertyValue().getValue());
    353                         }
    354                     }
    355                 }
    356             };
    357 
    358     private synchronized void handleSpeedChange(float newSpeed) {
    359         if (newSpeed == mCurrentMovingSpeed) {
    360             // Ignore if speed hasn't changed
    361             return;
    362         }
    363         int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue;
    364         if (currentDrivingState != CarDrivingStateEvent.DRIVING_STATE_MOVING) {
    365             // Ignore speed changes if the vehicle is not moving
    366             return;
    367         }
    368         mCurrentMovingSpeed = newSpeed;
    369         handleDispatchUxRestrictions(currentDrivingState, newSpeed);
    370     }
    371 
    372     /**
    373      * Handle dispatching UX restrictions change.
    374      *
    375      * @param currentDrivingState driving state of the vehicle
    376      * @param speed               speed of the vehicle
    377      */
    378     private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState,
    379             float speed) {
    380         CarUxRestrictions uxRestrictions;
    381         // Get UX restrictions from the parsed configuration XML or fall back to defaults if not
    382         // available.
    383         if (mFallbackToDefaults) {
    384             uxRestrictions = getDefaultRestrictions(currentDrivingState);
    385         } else {
    386             uxRestrictions = mHelper.getUxRestrictions(currentDrivingState, speed);
    387         }
    388 
    389         if (DBG) {
    390             Log.d(TAG, String.format("DO old->new: %b -> %b",
    391                     mCurrentUxRestrictions.isRequiresDistractionOptimization(),
    392                     uxRestrictions.isRequiresDistractionOptimization()));
    393             Log.d(TAG, String.format("UxR old->new: 0x%x -> 0x%x",
    394                     mCurrentUxRestrictions.getActiveRestrictions(),
    395                     uxRestrictions.getActiveRestrictions()));
    396         }
    397 
    398         if (mCurrentUxRestrictions.isSameRestrictions(uxRestrictions)) {
    399             // Ignore dispatching if the restrictions has not changed.
    400             return;
    401         }
    402         // for dumpsys logging
    403         StringBuilder extraInfo = new StringBuilder();
    404         extraInfo.append(
    405                 mCurrentUxRestrictions.isRequiresDistractionOptimization() ? "DO -> "
    406                         : "No DO -> ");
    407         extraInfo.append(
    408                 uxRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO");
    409         addTransitionLog(TAG, mCurrentUxRestrictions.getActiveRestrictions(),
    410                 uxRestrictions.getActiveRestrictions(), System.currentTimeMillis(),
    411                 extraInfo.toString());
    412 
    413         mCurrentUxRestrictions = uxRestrictions;
    414         if (DBG) {
    415             Log.d(TAG, "dispatching to " + mUxRClients.size() + " clients");
    416         }
    417         for (UxRestrictionsClient client : mUxRClients) {
    418             client.dispatchEventToClients(uxRestrictions);
    419         }
    420     }
    421 
    422     private CarUxRestrictions getDefaultRestrictions(@CarDrivingState int drivingState) {
    423         int restrictions;
    424         boolean requiresOpt = false;
    425         switch (drivingState) {
    426             case CarDrivingStateEvent.DRIVING_STATE_PARKED:
    427                 restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
    428                 break;
    429             case CarDrivingStateEvent.DRIVING_STATE_IDLING:
    430                 restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
    431                 requiresOpt = true;
    432                 break;
    433             case CarDrivingStateEvent.DRIVING_STATE_MOVING:
    434             default:
    435                 restrictions = CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
    436                 requiresOpt = true;
    437         }
    438         return mHelper.createUxRestrictionsEvent(requiresOpt, restrictions);
    439     }
    440 
    441     private void addTransitionLog(String name, int from, int to, long timestamp, String extra) {
    442         if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
    443             mTransitionLogs.remove();
    444         }
    445 
    446         Utils.TransitionLog tLog = new Utils.TransitionLog(name, from, to, timestamp, extra);
    447         mTransitionLogs.add(tLog);
    448     }
    449 
    450 }
    451