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