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 static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING; 20 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING; 21 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED; 22 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN; 23 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE; 24 25 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.car.Car; 30 import android.car.drivingstate.CarDrivingStateEvent; 31 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState; 32 import android.car.drivingstate.CarUxRestrictions; 33 import android.car.drivingstate.CarUxRestrictionsConfiguration; 34 import android.car.drivingstate.CarUxRestrictionsManager; 35 import android.car.drivingstate.ICarDrivingStateChangeListener; 36 import android.car.drivingstate.ICarUxRestrictionsChangeListener; 37 import android.car.drivingstate.ICarUxRestrictionsManager; 38 import android.car.hardware.CarPropertyValue; 39 import android.car.hardware.property.CarPropertyEvent; 40 import android.car.hardware.property.ICarPropertyEventListener; 41 import android.content.Context; 42 import android.content.pm.PackageManager; 43 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 44 import android.hardware.display.DisplayManager; 45 import android.os.Binder; 46 import android.os.Build; 47 import android.os.IBinder; 48 import android.os.Process; 49 import android.os.RemoteException; 50 import android.os.SystemClock; 51 import android.util.ArraySet; 52 import android.util.AtomicFile; 53 import android.util.JsonReader; 54 import android.util.JsonWriter; 55 import android.util.Log; 56 import android.util.Slog; 57 import android.view.Display; 58 import android.view.DisplayAddress; 59 60 import com.android.car.systeminterface.SystemInterface; 61 import com.android.internal.annotations.GuardedBy; 62 import com.android.internal.annotations.VisibleForTesting; 63 64 import org.xmlpull.v1.XmlPullParserException; 65 66 import java.io.File; 67 import java.io.FileOutputStream; 68 import java.io.IOException; 69 import java.io.InputStreamReader; 70 import java.io.OutputStreamWriter; 71 import java.io.PrintWriter; 72 import java.nio.charset.StandardCharsets; 73 import java.nio.file.Files; 74 import java.nio.file.Path; 75 import java.util.ArrayList; 76 import java.util.HashMap; 77 import java.util.LinkedList; 78 import java.util.List; 79 import java.util.Map; 80 import java.util.Set; 81 82 /** 83 * A service that listens to current driving state of the vehicle and maps it to the 84 * appropriate UX restrictions for that driving state. 85 * <p> 86 * <h1>UX Restrictions Configuration</h1> 87 * When this service starts, it will first try reading the configuration set through 88 * {@link #saveUxRestrictionsConfigurationForNextBoot(CarUxRestrictionsConfiguration)}. 89 * If one is not available, it will try reading the configuration saved in 90 * {@code R.xml.car_ux_restrictions_map}. If XML is somehow unavailable, it will 91 * fall back to a hard-coded configuration. 92 * <p> 93 * <h1>Multi-Display</h1> 94 * Only physical displays that are available at service initialization are recognized. 95 * This service does not support pluggable displays. 96 */ 97 public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements 98 CarServiceBase { 99 private static final String TAG = "CarUxR"; 100 private static final boolean DBG = false; 101 private static final int MAX_TRANSITION_LOG_SIZE = 20; 102 private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz 103 private static final float SPEED_NOT_AVAILABLE = -1.0F; 104 private static final byte DEFAULT_PORT = 0; 105 106 @VisibleForTesting 107 static final String CONFIG_FILENAME_PRODUCTION = "ux_restrictions_prod_config.json"; 108 @VisibleForTesting 109 static final String CONFIG_FILENAME_STAGED = "ux_restrictions_staged_config.json"; 110 111 private final Context mContext; 112 private final DisplayManager mDisplayManager; 113 private final CarDrivingStateService mDrivingStateService; 114 private final CarPropertyService mCarPropertyService; 115 // List of clients listening to UX restriction events. 116 private final List<UxRestrictionsClient> mUxRClients = new ArrayList<>(); 117 118 private float mCurrentMovingSpeed; 119 120 // Byte represents a physical port for display. 121 private byte mDefaultDisplayPhysicalPort; 122 private final List<Byte> mPhysicalPorts = new ArrayList<>(); 123 // This lookup caches the mapping from an int display id 124 // to a byte that represents a physical port. 125 private final Map<Integer, Byte> mPortLookup = new HashMap<>(); 126 private Map<Byte, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations; 127 private Map<Byte, CarUxRestrictions> mCurrentUxRestrictions; 128 129 @CarUxRestrictionsManager.UxRestrictionMode 130 private int mRestrictionMode = UX_RESTRICTION_MODE_BASELINE; 131 132 // Flag to disable broadcasting UXR changes - for development purposes 133 @GuardedBy("this") 134 private boolean mUxRChangeBroadcastEnabled = true; 135 // For dumpsys logging 136 private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>(); 137 CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService, CarPropertyService propertyService)138 public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService, 139 CarPropertyService propertyService) { 140 mContext = context; 141 mDisplayManager = mContext.getSystemService(DisplayManager.class); 142 mDrivingStateService = drvService; 143 mCarPropertyService = propertyService; 144 } 145 146 @Override init()147 public synchronized void init() { 148 mDefaultDisplayPhysicalPort = getDefaultDisplayPhysicalPort(); 149 150 initPhysicalPort(); 151 152 // Unrestricted until driving state information is received. During boot up, we don't want 153 // everything to be blocked until data is available from CarPropertyManager. If we start 154 // driving and we don't get speed or gear information, we have bigger problems. 155 mCurrentUxRestrictions = new HashMap<>(); 156 for (byte port : mPhysicalPorts) { 157 mCurrentUxRestrictions.put(port, createUnrestrictedRestrictions()); 158 } 159 160 // subscribe to driving State 161 mDrivingStateService.registerDrivingStateChangeListener( 162 mICarDrivingStateChangeEventListener); 163 // subscribe to property service for speed 164 mCarPropertyService.registerListener(VehicleProperty.PERF_VEHICLE_SPEED, 165 PROPERTY_UPDATE_RATE, mICarPropertyEventListener); 166 167 // At this point the driving state is known, which determines whether it's safe 168 // to promote staged new config. 169 mCarUxRestrictionsConfigurations = convertToMap(loadConfig()); 170 171 initializeUxRestrictions(); 172 } 173 174 @Override getConfigs()175 public List<CarUxRestrictionsConfiguration> getConfigs() { 176 return new ArrayList<>(mCarUxRestrictionsConfigurations.values()); 177 } 178 179 /** 180 * Loads UX restrictions configurations and returns them. 181 * 182 * <p>Reads config from the following sources in order: 183 * <ol> 184 * <li>saved config set by 185 * {@link #saveUxRestrictionsConfigurationForNextBoot(CarUxRestrictionsConfiguration)}; 186 * <li>XML resource config from {@code R.xml.car_ux_restrictions_map}; 187 * <li>hardcoded default config. 188 * </ol> 189 * 190 * This method attempts to promote staged config file. Doing which depends on driving state. 191 */ 192 @VisibleForTesting loadConfig()193 synchronized List<CarUxRestrictionsConfiguration> loadConfig() { 194 promoteStagedConfig(); 195 List<CarUxRestrictionsConfiguration> configs; 196 197 // Production config, if available, is the first choice. 198 File prodConfig = getFile(CONFIG_FILENAME_PRODUCTION); 199 if (prodConfig.exists()) { 200 logd("Attempting to read production config"); 201 configs = readPersistedConfig(prodConfig); 202 if (configs != null) { 203 return configs; 204 } 205 } 206 207 // XML config is the second choice. 208 logd("Attempting to read config from XML resource"); 209 configs = readXmlConfig(); 210 if (configs != null) { 211 return configs; 212 } 213 214 // This should rarely happen. 215 Log.w(TAG, "Creating default config"); 216 217 configs = new ArrayList<>(); 218 for (byte port : mPhysicalPorts) { 219 configs.add(createDefaultConfig(port)); 220 } 221 return configs; 222 } 223 getFile(String filename)224 private File getFile(String filename) { 225 SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class); 226 return new File(systemInterface.getSystemCarDir(), filename); 227 } 228 229 @Nullable readXmlConfig()230 private List<CarUxRestrictionsConfiguration> readXmlConfig() { 231 try { 232 return CarUxRestrictionsConfigurationXmlParser.parse( 233 mContext, R.xml.car_ux_restrictions_map); 234 } catch (IOException | XmlPullParserException e) { 235 Log.e(TAG, "Could not read config from XML resource", e); 236 } 237 return null; 238 } 239 promoteStagedConfig()240 private void promoteStagedConfig() { 241 Path stagedConfig = getFile(CONFIG_FILENAME_STAGED).toPath(); 242 243 CarDrivingStateEvent currentDrivingStateEvent = 244 mDrivingStateService.getCurrentDrivingState(); 245 // Only promote staged config when car is parked. 246 if (currentDrivingStateEvent != null 247 && currentDrivingStateEvent.eventValue == DRIVING_STATE_PARKED 248 && Files.exists(stagedConfig)) { 249 250 Path prod = getFile(CONFIG_FILENAME_PRODUCTION).toPath(); 251 try { 252 logd("Attempting to promote stage config"); 253 Files.move(stagedConfig, prod, REPLACE_EXISTING); 254 } catch (IOException e) { 255 Log.e(TAG, "Could not promote state config", e); 256 } 257 } 258 } 259 260 // Update current restrictions by getting the current driving state and speed. initializeUxRestrictions()261 private void initializeUxRestrictions() { 262 CarDrivingStateEvent currentDrivingStateEvent = 263 mDrivingStateService.getCurrentDrivingState(); 264 // if we don't have enough information from the CarPropertyService to compute the UX 265 // restrictions, then leave the UX restrictions unchanged from what it was initialized to 266 // in the constructor. 267 if (currentDrivingStateEvent == null 268 || currentDrivingStateEvent.eventValue == DRIVING_STATE_UNKNOWN) { 269 return; 270 } 271 int currentDrivingState = currentDrivingStateEvent.eventValue; 272 Float currentSpeed = getCurrentSpeed(); 273 if (currentSpeed == SPEED_NOT_AVAILABLE) { 274 return; 275 } 276 // At this point the underlying CarPropertyService has provided us enough information to 277 // compute the UX restrictions that could be potentially different from the initial UX 278 // restrictions. 279 handleDispatchUxRestrictions(currentDrivingState, currentSpeed); 280 } 281 getCurrentSpeed()282 private Float getCurrentSpeed() { 283 CarPropertyValue value = mCarPropertyService.getProperty(VehicleProperty.PERF_VEHICLE_SPEED, 284 0); 285 if (value != null) { 286 return (Float) value.getValue(); 287 } 288 return SPEED_NOT_AVAILABLE; 289 } 290 291 @Override release()292 public synchronized void release() { 293 for (UxRestrictionsClient client : mUxRClients) { 294 client.listenerBinder.unlinkToDeath(client, 0); 295 } 296 mUxRClients.clear(); 297 mDrivingStateService.unregisterDrivingStateChangeListener( 298 mICarDrivingStateChangeEventListener); 299 } 300 301 // Binder methods 302 303 /** 304 * Registers a {@link ICarUxRestrictionsChangeListener} to be notified for changes to the UX 305 * restrictions. 306 * 307 * @param listener Listener to register 308 * @param displayId UX restrictions on this display will be notified. 309 */ 310 @Override registerUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener, int displayId)311 public synchronized void registerUxRestrictionsChangeListener( 312 ICarUxRestrictionsChangeListener listener, int displayId) { 313 if (listener == null) { 314 Log.e(TAG, "registerUxRestrictionsChangeListener(): listener null"); 315 throw new IllegalArgumentException("Listener is null"); 316 } 317 // If a new client is registering, create a new DrivingStateClient and add it to the list 318 // of listening clients. 319 UxRestrictionsClient client = findUxRestrictionsClient(listener); 320 if (client == null) { 321 client = new UxRestrictionsClient(listener, displayId); 322 try { 323 listener.asBinder().linkToDeath(client, 0); 324 } catch (RemoteException e) { 325 Log.e(TAG, "Cannot link death recipient to binder " + e); 326 } 327 mUxRClients.add(client); 328 } 329 return; 330 } 331 332 /** 333 * Iterates through the list of registered UX Restrictions clients - 334 * {@link UxRestrictionsClient} and finds if the given client is already registered. 335 * 336 * @param listener Listener to look for. 337 * @return the {@link UxRestrictionsClient} if found, null if not 338 */ 339 @Nullable findUxRestrictionsClient( ICarUxRestrictionsChangeListener listener)340 private UxRestrictionsClient findUxRestrictionsClient( 341 ICarUxRestrictionsChangeListener listener) { 342 IBinder binder = listener.asBinder(); 343 for (UxRestrictionsClient client : mUxRClients) { 344 if (client.isHoldingBinder(binder)) { 345 return client; 346 } 347 } 348 return null; 349 } 350 351 /** 352 * Unregister the given UX Restrictions listener 353 * 354 * @param listener client to unregister 355 */ 356 @Override unregisterUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener)357 public synchronized void unregisterUxRestrictionsChangeListener( 358 ICarUxRestrictionsChangeListener listener) { 359 if (listener == null) { 360 Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null"); 361 throw new IllegalArgumentException("Listener is null"); 362 } 363 364 UxRestrictionsClient client = findUxRestrictionsClient(listener); 365 if (client == null) { 366 Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener was not previously " 367 + "registered"); 368 return; 369 } 370 listener.asBinder().unlinkToDeath(client, 0); 371 mUxRClients.remove(client); 372 } 373 374 /** 375 * Gets the current UX restrictions for a display. 376 * 377 * @param displayId UX restrictions on this display will be returned. 378 */ 379 @Override getCurrentUxRestrictions(int displayId)380 public synchronized CarUxRestrictions getCurrentUxRestrictions(int displayId) { 381 CarUxRestrictions restrictions = mCurrentUxRestrictions.get(getPhysicalPort(displayId)); 382 if (restrictions == null) { 383 Log.e(TAG, String.format( 384 "Restrictions are null for displayId:%d. Returning full restrictions.", 385 displayId)); 386 restrictions = createFullyRestrictedRestrictions(); 387 } 388 return restrictions; 389 } 390 391 /** 392 * Convenience method to retrieve restrictions for default display. 393 */ 394 @Nullable getCurrentUxRestrictions()395 public synchronized CarUxRestrictions getCurrentUxRestrictions() { 396 return getCurrentUxRestrictions(Display.DEFAULT_DISPLAY); 397 } 398 399 @Override saveUxRestrictionsConfigurationForNextBoot( List<CarUxRestrictionsConfiguration> configs)400 public synchronized boolean saveUxRestrictionsConfigurationForNextBoot( 401 List<CarUxRestrictionsConfiguration> configs) { 402 ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 403 404 validateConfigs(configs); 405 406 return persistConfig(configs, CONFIG_FILENAME_STAGED); 407 } 408 409 @Override 410 @Nullable getStagedConfigs()411 public List<CarUxRestrictionsConfiguration> getStagedConfigs() { 412 File stagedConfig = getFile(CONFIG_FILENAME_STAGED); 413 if (stagedConfig.exists()) { 414 logd("Attempting to read staged config"); 415 return readPersistedConfig(stagedConfig); 416 } else { 417 return null; 418 } 419 } 420 421 /** 422 * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to 423 * be applied in the same driving state. Restrictions for each mode can be configured through 424 * {@link CarUxRestrictionsConfiguration}. 425 * 426 * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}. 427 * 428 * @param mode See values in {@link CarUxRestrictionsManager.UxRestrictionMode}. 429 * @return {@code true} if mode was successfully changed; {@code false} otherwise. 430 * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions 431 * @see CarUxRestrictionsConfiguration.Builder 432 */ 433 @Override setRestrictionMode( @arUxRestrictionsManager.UxRestrictionMode int mode)434 public synchronized boolean setRestrictionMode( 435 @CarUxRestrictionsManager.UxRestrictionMode int mode) { 436 if (mRestrictionMode == mode) { 437 return true; 438 } 439 440 addTransitionLog(TAG, mRestrictionMode, mode, System.currentTimeMillis(), 441 "Restriction mode"); 442 mRestrictionMode = mode; 443 logd("Set restriction mode to: " + CarUxRestrictionsManager.modeToString(mode)); 444 445 handleDispatchUxRestrictions( 446 mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed()); 447 return true; 448 } 449 450 @Override 451 @CarUxRestrictionsManager.UxRestrictionMode getRestrictionMode()452 public synchronized int getRestrictionMode() { 453 return mRestrictionMode; 454 } 455 456 /** 457 * Writes configuration into the specified file. 458 * 459 * IO access on file is not thread safe. Caller should ensure threading protection. 460 */ persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename)461 private boolean persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename) { 462 File file = getFile(filename); 463 AtomicFile stagedFile = new AtomicFile(file); 464 FileOutputStream fos; 465 try { 466 fos = stagedFile.startWrite(); 467 } catch (IOException e) { 468 Log.e(TAG, "Could not open file to persist config", e); 469 return false; 470 } 471 try (JsonWriter jsonWriter = new JsonWriter( 472 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) { 473 jsonWriter.beginArray(); 474 for (CarUxRestrictionsConfiguration config : configs) { 475 config.writeJson(jsonWriter); 476 } 477 jsonWriter.endArray(); 478 } catch (IOException e) { 479 Log.e(TAG, "Could not persist config", e); 480 stagedFile.failWrite(fos); 481 return false; 482 } 483 stagedFile.finishWrite(fos); 484 return true; 485 } 486 487 @Nullable readPersistedConfig(File file)488 private List<CarUxRestrictionsConfiguration> readPersistedConfig(File file) { 489 if (!file.exists()) { 490 Log.e(TAG, "Could not find config file: " + file.getName()); 491 return null; 492 } 493 494 AtomicFile configFile = new AtomicFile(file); 495 try (JsonReader reader = new JsonReader( 496 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) { 497 List<CarUxRestrictionsConfiguration> configs = new ArrayList<>(); 498 reader.beginArray(); 499 while (reader.hasNext()) { 500 configs.add(CarUxRestrictionsConfiguration.readJson(reader)); 501 } 502 reader.endArray(); 503 return configs; 504 } catch (IOException e) { 505 Log.e(TAG, "Could not read persisted config file " + file.getName(), e); 506 } 507 return null; 508 } 509 510 /** 511 * Enable/disable UX restrictions change broadcast blocking. 512 * Setting this to true will stop broadcasts of UX restriction change to listeners. 513 * This method works only on debug builds and the caller of this method needs to have the same 514 * signature of the car service. 515 */ setUxRChangeBroadcastEnabled(boolean enable)516 public synchronized void setUxRChangeBroadcastEnabled(boolean enable) { 517 if (!isDebugBuild()) { 518 Log.e(TAG, "Cannot set UX restriction change broadcast."); 519 return; 520 } 521 // Check if the caller has the same signature as that of the car service. 522 if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid()) 523 != PackageManager.SIGNATURE_MATCH) { 524 throw new SecurityException( 525 "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid()) 526 + " does not have the right signature"); 527 } 528 if (enable) { 529 // if enabling it back, send the current restrictions 530 mUxRChangeBroadcastEnabled = enable; 531 handleDispatchUxRestrictions(mDrivingStateService.getCurrentDrivingState().eventValue, 532 getCurrentSpeed()); 533 } else { 534 // fake parked state, so if the system is currently restricted, the restrictions are 535 // relaxed. 536 handleDispatchUxRestrictions(DRIVING_STATE_PARKED, 0); 537 mUxRChangeBroadcastEnabled = enable; 538 } 539 } 540 isDebugBuild()541 private boolean isDebugBuild() { 542 return Build.IS_USERDEBUG || Build.IS_ENG; 543 } 544 545 /** 546 * Class that holds onto client related information - listener interface, process that hosts the 547 * binder object etc. 548 * It also registers for death notifications of the host. 549 */ 550 private class UxRestrictionsClient implements IBinder.DeathRecipient { 551 private final IBinder listenerBinder; 552 private final ICarUxRestrictionsChangeListener listener; 553 private final int mDisplayId; 554 UxRestrictionsClient(ICarUxRestrictionsChangeListener l, int displayId)555 UxRestrictionsClient(ICarUxRestrictionsChangeListener l, int displayId) { 556 listener = l; 557 listenerBinder = l.asBinder(); 558 mDisplayId = displayId; 559 } 560 561 @Override binderDied()562 public void binderDied() { 563 logd("Binder died " + listenerBinder); 564 listenerBinder.unlinkToDeath(this, 0); 565 synchronized (CarUxRestrictionsManagerService.this) { 566 mUxRClients.remove(this); 567 } 568 } 569 570 /** 571 * Returns if the given binder object matches to what this client info holds. 572 * Used to check if the listener asking to be registered is already registered. 573 * 574 * @return true if matches, false if not 575 */ isHoldingBinder(IBinder binder)576 public boolean isHoldingBinder(IBinder binder) { 577 return listenerBinder == binder; 578 } 579 580 /** 581 * Dispatch the event to the listener 582 * 583 * @param event {@link CarUxRestrictions}. 584 */ dispatchEventToClients(CarUxRestrictions event)585 public void dispatchEventToClients(CarUxRestrictions event) { 586 if (event == null) { 587 return; 588 } 589 try { 590 listener.onUxRestrictionsChanged(event); 591 } catch (RemoteException e) { 592 Log.e(TAG, "Dispatch to listener failed", e); 593 } 594 } 595 } 596 597 @Override dump(PrintWriter writer)598 public void dump(PrintWriter writer) { 599 writer.println("*CarUxRestrictionsManagerService*"); 600 for (byte port : mCurrentUxRestrictions.keySet()) { 601 CarUxRestrictions restrictions = mCurrentUxRestrictions.get(port); 602 writer.printf("Port: 0x%02X UXR: %s\n", port, restrictions.toString()); 603 } 604 if (isDebugBuild()) { 605 writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled); 606 } 607 608 writer.println("UX Restriction configurations:"); 609 for (CarUxRestrictionsConfiguration config : mCarUxRestrictionsConfigurations.values()) { 610 config.dump(writer); 611 } 612 writer.println("UX Restriction change log:"); 613 for (Utils.TransitionLog tlog : mTransitionLogs) { 614 writer.println(tlog); 615 } 616 } 617 618 /** 619 * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService} 620 * for getting driving state change notifications. 621 */ 622 private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener = 623 new ICarDrivingStateChangeListener.Stub() { 624 @Override 625 public void onDrivingStateChanged(CarDrivingStateEvent event) { 626 logd("Driving State Changed:" + event.eventValue); 627 handleDrivingStateEvent(event); 628 } 629 }; 630 631 /** 632 * Handle the driving state change events coming from the {@link CarDrivingStateService}. 633 * Map the driving state to the corresponding UX Restrictions and dispatch the 634 * UX Restriction change to the registered clients. 635 */ handleDrivingStateEvent(CarDrivingStateEvent event)636 private synchronized void handleDrivingStateEvent(CarDrivingStateEvent event) { 637 if (event == null) { 638 return; 639 } 640 int drivingState = event.eventValue; 641 Float speed = getCurrentSpeed(); 642 643 if (speed != SPEED_NOT_AVAILABLE) { 644 mCurrentMovingSpeed = speed; 645 } else if (drivingState == DRIVING_STATE_PARKED 646 || drivingState == DRIVING_STATE_UNKNOWN) { 647 // If speed is unavailable, but the driving state is parked or unknown, it can still be 648 // handled. 649 logd("Speed null when driving state is: " + drivingState); 650 mCurrentMovingSpeed = 0; 651 } else { 652 // If we get here with driving state != parked or unknown && speed == null, 653 // something is wrong. CarDrivingStateService could not have inferred idling or moving 654 // when speed is not available 655 Log.e(TAG, "Unexpected: Speed null when driving state is: " + drivingState); 656 return; 657 } 658 handleDispatchUxRestrictions(drivingState, mCurrentMovingSpeed); 659 } 660 661 /** 662 * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting 663 * speed change notifications. 664 */ 665 private final ICarPropertyEventListener mICarPropertyEventListener = 666 new ICarPropertyEventListener.Stub() { 667 @Override 668 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 669 for (CarPropertyEvent event : events) { 670 if ((event.getEventType() 671 == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) 672 && (event.getCarPropertyValue().getPropertyId() 673 == VehicleProperty.PERF_VEHICLE_SPEED)) { 674 handleSpeedChange((Float) event.getCarPropertyValue().getValue()); 675 } 676 } 677 } 678 }; 679 handleSpeedChange(float newSpeed)680 private synchronized void handleSpeedChange(float newSpeed) { 681 if (newSpeed == mCurrentMovingSpeed) { 682 // Ignore if speed hasn't changed 683 return; 684 } 685 int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue; 686 if (currentDrivingState != DRIVING_STATE_MOVING) { 687 // Ignore speed changes if the vehicle is not moving 688 return; 689 } 690 mCurrentMovingSpeed = newSpeed; 691 handleDispatchUxRestrictions(currentDrivingState, newSpeed); 692 } 693 694 /** 695 * Handle dispatching UX restrictions change. 696 * 697 * @param currentDrivingState driving state of the vehicle 698 * @param speed speed of the vehicle 699 */ handleDispatchUxRestrictions(@arDrivingState int currentDrivingState, float speed)700 private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState, 701 float speed) { 702 if (isDebugBuild() && !mUxRChangeBroadcastEnabled) { 703 Log.d(TAG, "Not dispatching UX Restriction due to setting"); 704 return; 705 } 706 707 Map<Byte, CarUxRestrictions> newUxRestrictions = new HashMap<>(); 708 for (byte port : mPhysicalPorts) { 709 CarUxRestrictionsConfiguration config = mCarUxRestrictionsConfigurations.get(port); 710 if (config == null) { 711 continue; 712 } 713 714 CarUxRestrictions uxRestrictions = config.getUxRestrictions( 715 currentDrivingState, speed, mRestrictionMode); 716 logd(String.format("Display port 0x%02x\tDO old->new: %b -> %b", 717 port, 718 mCurrentUxRestrictions.get(port).isRequiresDistractionOptimization(), 719 uxRestrictions.isRequiresDistractionOptimization())); 720 logd(String.format("Display port 0x%02x\tUxR old->new: 0x%x -> 0x%x", 721 port, 722 mCurrentUxRestrictions.get(port).getActiveRestrictions(), 723 uxRestrictions.getActiveRestrictions())); 724 newUxRestrictions.put(port, uxRestrictions); 725 } 726 727 // Ignore dispatching if the restrictions has not changed. 728 Set<Byte> displayToDispatch = new ArraySet<>(); 729 for (byte port : newUxRestrictions.keySet()) { 730 if (!mCurrentUxRestrictions.containsKey(port)) { 731 // This should never happen. 732 Log.wtf(TAG, "Unrecognized port:" + port); 733 continue; 734 } 735 CarUxRestrictions uxRestrictions = newUxRestrictions.get(port); 736 if (!mCurrentUxRestrictions.get(port).isSameRestrictions(uxRestrictions)) { 737 displayToDispatch.add(port); 738 } 739 } 740 if (displayToDispatch.isEmpty()) { 741 return; 742 } 743 744 for (byte port : displayToDispatch) { 745 addTransitionLog( 746 mCurrentUxRestrictions.get(port), newUxRestrictions.get(port)); 747 } 748 749 logd("dispatching to clients"); 750 for (UxRestrictionsClient client : mUxRClients) { 751 Byte clientDisplayPort = getPhysicalPort(client.mDisplayId); 752 if (clientDisplayPort == null) { 753 clientDisplayPort = mDefaultDisplayPhysicalPort; 754 } 755 if (displayToDispatch.contains(clientDisplayPort)) { 756 client.dispatchEventToClients(newUxRestrictions.get(clientDisplayPort)); 757 } 758 } 759 760 mCurrentUxRestrictions = newUxRestrictions; 761 } 762 getDefaultDisplayPhysicalPort()763 private byte getDefaultDisplayPhysicalPort() { 764 Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); 765 DisplayAddress.Physical address = (DisplayAddress.Physical) defaultDisplay.getAddress(); 766 767 if (address == null) { 768 Log.w(TAG, "Default display does not have physical display port."); 769 return DEFAULT_PORT; 770 } 771 return address.getPort(); 772 } 773 initPhysicalPort()774 private void initPhysicalPort() { 775 for (Display display : mDisplayManager.getDisplays()) { 776 if (display.getType() == Display.TYPE_VIRTUAL) { 777 continue; 778 } 779 780 if (display.getDisplayId() == Display.DEFAULT_DISPLAY && display.getAddress() == null) { 781 // Assume default display is a physical display so assign an address if it 782 // does not have one (possibly due to lower graphic driver version). 783 if (Log.isLoggable(TAG, Log.INFO)) { 784 Log.i(TAG, "Default display does not have display address. Using default."); 785 } 786 mPhysicalPorts.add(mDefaultDisplayPhysicalPort); 787 } else if (display.getAddress() instanceof DisplayAddress.Physical) { 788 byte port = ((DisplayAddress.Physical) display.getAddress()).getPort(); 789 if (Log.isLoggable(TAG, Log.INFO)) { 790 Log.i(TAG, String.format( 791 "Display %d uses port %d", display.getDisplayId(), port)); 792 } 793 mPhysicalPorts.add(port); 794 } else { 795 Log.w(TAG, "At init non-virtual display has a non-physical display address: " 796 + display); 797 } 798 } 799 } 800 convertToMap( List<CarUxRestrictionsConfiguration> configs)801 private Map<Byte, CarUxRestrictionsConfiguration> convertToMap( 802 List<CarUxRestrictionsConfiguration> configs) { 803 validateConfigs(configs); 804 805 Map<Byte, CarUxRestrictionsConfiguration> result = new HashMap<>(); 806 if (configs.size() == 1) { 807 CarUxRestrictionsConfiguration config = configs.get(0); 808 byte port = config.getPhysicalPort() == null 809 ? mDefaultDisplayPhysicalPort 810 : config.getPhysicalPort(); 811 result.put(port, config); 812 } else { 813 for (CarUxRestrictionsConfiguration config : configs) { 814 result.put(config.getPhysicalPort(), config); 815 } 816 } 817 return result; 818 } 819 820 /** 821 * Validates configs for multi-display: 822 * - share the same restrictions parameters; 823 * - each sets display port; 824 * - each has unique display port. 825 */ 826 @VisibleForTesting validateConfigs(List<CarUxRestrictionsConfiguration> configs)827 void validateConfigs(List<CarUxRestrictionsConfiguration> configs) { 828 if (configs.size() == 0) { 829 throw new IllegalArgumentException("Empty configuration."); 830 } 831 832 if (configs.size() == 1) { 833 return; 834 } 835 836 CarUxRestrictionsConfiguration first = configs.get(0); 837 Set<Byte> existingPorts = new ArraySet<>(); 838 for (CarUxRestrictionsConfiguration config : configs) { 839 if (!config.hasSameParameters(first)) { 840 // Input should have the same restriction parameters because: 841 // - it doesn't make sense otherwise; and 842 // - in format it matches how xml can only specify one set of parameters. 843 throw new IllegalArgumentException( 844 "Configurations should have the same restrictions parameters."); 845 } 846 847 Byte port = config.getPhysicalPort(); 848 if (port == null) { 849 // Size was checked above; safe to assume there are multiple configs. 850 throw new IllegalArgumentException( 851 "Input contains multiple configurations; each must set physical port."); 852 } 853 if (existingPorts.contains(port)) { 854 throw new IllegalArgumentException("Multiple configurations for port " + port); 855 } 856 857 existingPorts.add(port); 858 } 859 } 860 861 /** 862 * Returns the physical port byte id for the display or {@code null} if {@link 863 * DisplayManager#getDisplay(int)} is not aware of the provided id. 864 */ 865 @Nullable getPhysicalPort(int displayId)866 private Byte getPhysicalPort(int displayId) { 867 if (!mPortLookup.containsKey(displayId)) { 868 Display display = mDisplayManager.getDisplay(displayId); 869 if (display == null) { 870 Log.w(TAG, "Could not retrieve display for id: " + displayId); 871 return null; 872 } 873 byte port = getPhysicalPort(display); 874 mPortLookup.put(displayId, port); 875 } 876 return mPortLookup.get(displayId); 877 } 878 getPhysicalPort(@onNull Display display)879 private byte getPhysicalPort(@NonNull Display display) { 880 if (display.getType() == Display.TYPE_VIRTUAL) { 881 // We require all virtual displays to be launched on default display. 882 return mDefaultDisplayPhysicalPort; 883 } 884 885 DisplayAddress address = display.getAddress(); 886 if (address == null) { 887 Log.e(TAG, "Display " + display 888 + " is not a virtual display but has null DisplayAddress."); 889 return mDefaultDisplayPhysicalPort; 890 } else if (!(address instanceof DisplayAddress.Physical)) { 891 Log.e(TAG, "Display " + display + " has non-physical address: " + address); 892 return mDefaultDisplayPhysicalPort; 893 } else { 894 return ((DisplayAddress.Physical) address).getPort(); 895 } 896 } 897 createUnrestrictedRestrictions()898 private CarUxRestrictions createUnrestrictedRestrictions() { 899 return new CarUxRestrictions.Builder(/* reqOpt= */ false, 900 CarUxRestrictions.UX_RESTRICTIONS_BASELINE, SystemClock.elapsedRealtimeNanos()) 901 .build(); 902 } 903 createFullyRestrictedRestrictions()904 private CarUxRestrictions createFullyRestrictedRestrictions() { 905 return new CarUxRestrictions.Builder( 906 /*reqOpt= */ true, 907 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED, 908 SystemClock.elapsedRealtimeNanos()).build(); 909 } 910 createDefaultConfig(byte port)911 CarUxRestrictionsConfiguration createDefaultConfig(byte port) { 912 return new CarUxRestrictionsConfiguration.Builder() 913 .setPhysicalPort(port) 914 .setUxRestrictions(DRIVING_STATE_PARKED, 915 false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE) 916 .setUxRestrictions(DRIVING_STATE_IDLING, 917 false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE) 918 .setUxRestrictions(DRIVING_STATE_MOVING, 919 true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED) 920 .setUxRestrictions(DRIVING_STATE_UNKNOWN, 921 true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED) 922 .build(); 923 } 924 addTransitionLog(String name, int from, int to, long timestamp, String extra)925 private void addTransitionLog(String name, int from, int to, long timestamp, String extra) { 926 if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) { 927 mTransitionLogs.remove(); 928 } 929 930 Utils.TransitionLog tLog = new Utils.TransitionLog(name, from, to, timestamp, extra); 931 mTransitionLogs.add(tLog); 932 } 933 addTransitionLog( CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions)934 private void addTransitionLog( 935 CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions) { 936 if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) { 937 mTransitionLogs.remove(); 938 } 939 StringBuilder extra = new StringBuilder(); 940 extra.append(oldRestrictions.isRequiresDistractionOptimization() ? "DO -> " : "No DO -> "); 941 extra.append(newRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO"); 942 943 Utils.TransitionLog tLog = new Utils.TransitionLog(TAG, 944 oldRestrictions.getActiveRestrictions(), newRestrictions.getActiveRestrictions(), 945 System.currentTimeMillis(), extra.toString()); 946 mTransitionLogs.add(tLog); 947 } 948 logd(String msg)949 private static void logd(String msg) { 950 if (DBG) { 951 Slog.d(TAG, msg); 952 } 953 } 954 } 955