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