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.CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 20 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING; 21 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING; 22 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED; 23 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN; 24 import static android.car.drivingstate.CarUxRestrictionsConfiguration.Builder.SpeedRange.MAX_SPEED; 25 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE; 26 27 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 28 29 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 30 31 import android.annotation.FloatRange; 32 import android.annotation.IntDef; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.car.Car; 36 import android.car.CarOccupantZoneManager; 37 import android.car.CarOccupantZoneManager.OccupantZoneInfo; 38 import android.car.ICarOccupantZoneCallback; 39 import android.car.VehicleAreaType; 40 import android.car.builtin.os.BinderHelper; 41 import android.car.builtin.os.BuildHelper; 42 import android.car.builtin.util.Slogf; 43 import android.car.drivingstate.CarDrivingStateEvent; 44 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState; 45 import android.car.drivingstate.CarUxRestrictions; 46 import android.car.drivingstate.CarUxRestrictionsConfiguration; 47 import android.car.drivingstate.CarUxRestrictionsManager; 48 import android.car.drivingstate.ICarDrivingStateChangeListener; 49 import android.car.drivingstate.ICarUxRestrictionsChangeListener; 50 import android.car.drivingstate.ICarUxRestrictionsManager; 51 import android.car.hardware.CarPropertyValue; 52 import android.car.hardware.property.CarPropertyEvent; 53 import android.car.hardware.property.ICarPropertyEventListener; 54 import android.content.Context; 55 import android.content.pm.PackageManager; 56 import android.hardware.automotive.vehicle.VehicleProperty; 57 import android.hardware.display.DisplayManager; 58 import android.os.Binder; 59 import android.os.Handler; 60 import android.os.HandlerThread; 61 import android.os.Process; 62 import android.os.RemoteCallbackList; 63 import android.os.RemoteException; 64 import android.os.SystemClock; 65 import android.util.ArrayMap; 66 import android.util.ArraySet; 67 import android.util.AtomicFile; 68 import android.util.JsonReader; 69 import android.util.JsonToken; 70 import android.util.JsonWriter; 71 import android.util.Log; 72 import android.util.SparseArray; 73 import android.util.proto.ProtoOutputStream; 74 import android.view.Display; 75 76 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 77 import com.android.car.internal.util.DebugUtils; 78 import com.android.car.internal.util.IndentingPrintWriter; 79 import com.android.car.internal.util.IntArray; 80 import com.android.car.systeminterface.SystemInterface; 81 import com.android.car.util.TransitionLog; 82 import com.android.internal.annotations.GuardedBy; 83 import com.android.internal.annotations.VisibleForTesting; 84 85 import org.xmlpull.v1.XmlPullParserException; 86 87 import java.io.File; 88 import java.io.FileOutputStream; 89 import java.io.IOException; 90 import java.io.InputStreamReader; 91 import java.io.OutputStreamWriter; 92 import java.lang.annotation.Retention; 93 import java.lang.annotation.RetentionPolicy; 94 import java.nio.charset.StandardCharsets; 95 import java.nio.file.Files; 96 import java.nio.file.Path; 97 import java.util.ArrayList; 98 import java.util.Arrays; 99 import java.util.Collection; 100 import java.util.HashSet; 101 import java.util.LinkedList; 102 import java.util.List; 103 import java.util.Map; 104 import java.util.Objects; 105 import java.util.Optional; 106 import java.util.Set; 107 108 /** 109 * A service that listens to current driving state of the vehicle and maps it to the 110 * appropriate UX restrictions for that driving state. 111 * <p> 112 * <h1>UX Restrictions Configuration</h1> 113 * When this service starts, it will first try reading the configuration set through 114 * {@link #saveUxRestrictionsConfigurationForNextBoot(List)}. 115 * If one is not available, it will try reading the configuration saved in 116 * {@code R.xml.car_ux_restrictions_map}. If XML is somehow unavailable, it will 117 * fall back to a hard-coded configuration. 118 * <p> 119 * <h1>Multi-Display</h1> 120 * Only physical displays that are available at service initialization are recognized. 121 * This service does not support pluggable displays. 122 */ 123 public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements 124 CarServiceBase { 125 private static final String TAG = CarLog.tagFor(CarUxRestrictionsManagerService.class); 126 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 127 private static final int MAX_TRANSITION_LOG_SIZE = 20; 128 private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz 129 130 // Using 8 as the initial capacity for several data structures as the number of displays 131 // will most likely be below 8. 132 private static final int INITIAL_DISPLAYS_SIZE = 8; 133 134 private static final int UNKNOWN_JSON_SCHEMA_VERSION = -1; 135 private static final int JSON_SCHEMA_VERSION_V1 = 1; 136 private static final int JSON_SCHEMA_VERSION_V2 = 2; 137 138 @IntDef({UNKNOWN_JSON_SCHEMA_VERSION, JSON_SCHEMA_VERSION_V1, JSON_SCHEMA_VERSION_V2}) 139 @Retention(RetentionPolicy.SOURCE) 140 private @interface JsonSchemaVersion {} 141 142 private static final String JSON_NAME_SCHEMA_VERSION = "schema_version"; 143 private static final String JSON_NAME_RESTRICTIONS = "restrictions"; 144 145 @VisibleForTesting 146 static final String CONFIG_FILENAME_PRODUCTION = "ux_restrictions_prod_config.json"; 147 @VisibleForTesting 148 static final String CONFIG_FILENAME_STAGED = "ux_restrictions_staged_config.json"; 149 150 private final Context mContext; 151 private final DisplayManager mDisplayManager; 152 private final CarDrivingStateService mDrivingStateService; 153 private final CarPropertyService mCarPropertyService; 154 private final CarOccupantZoneService mCarOccupantZoneService; 155 private final HandlerThread mClientDispatchThread = CarServiceUtils.getHandlerThread( 156 getClass().getSimpleName()); 157 private final Handler mClientDispatchHandler = new Handler(mClientDispatchThread.getLooper()); 158 private final RemoteCallbackList<ICarUxRestrictionsChangeListener> mUxRClients = 159 new RemoteCallbackList<>(); 160 161 /** 162 * Metadata associated with a binder callback. 163 */ 164 private static class RemoteCallbackListCookie { 165 final int mDisplayId; 166 RemoteCallbackListCookie(int displayId)167 RemoteCallbackListCookie(int displayId) { 168 mDisplayId = displayId; 169 } 170 } 171 172 private final Object mLock = new Object(); 173 174 // DisplayIdentifier identifies a display by the combination of occupant zone id and display 175 // type. 176 private static final class DisplayIdentifier { 177 public final int occupantZoneId; 178 public final int displayType; 179 private int mHashCode; 180 DisplayIdentifier(int occupantZoneId, int displayType)181 DisplayIdentifier(int occupantZoneId, int displayType) { 182 this.displayType = displayType; 183 this.occupantZoneId = occupantZoneId; 184 } 185 186 @Override equals(Object o)187 public boolean equals(Object o) { 188 if (this == o) { 189 return true; 190 } 191 if (o == null || !(o instanceof DisplayIdentifier)) { 192 return false; 193 } 194 DisplayIdentifier that = (DisplayIdentifier) o; 195 return occupantZoneId == that.occupantZoneId 196 && displayType == that.displayType; 197 } 198 199 @Override hashCode()200 public int hashCode() { 201 if (mHashCode == 0) { 202 mHashCode = Objects.hash(occupantZoneId, displayType); 203 } 204 return mHashCode; 205 } 206 207 @Override toString()208 public String toString() { 209 return "{occupantZoneId=" + occupantZoneId + " displayType=" 210 + Integer.toHexString(displayType) + "}"; 211 } 212 } 213 214 // In memory representation of Ux Restrictions config, key'ed by DisplayIdentifier 215 // (occupant zone id, display type). 216 @GuardedBy("mLock") 217 private Map<DisplayIdentifier, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations; 218 219 // Current Ux Restrictions, key'ed by logical display id. 220 @GuardedBy("mLock") 221 private SparseArray<CarUxRestrictions> mCurrentUxRestrictions; 222 223 @GuardedBy("mLock") 224 private String mRestrictionMode = UX_RESTRICTION_MODE_BASELINE; 225 226 @GuardedBy("mLock") 227 private float mCurrentMovingSpeed; 228 229 // DisplayIdentifier for the default display. 230 @GuardedBy("mLock") 231 private DisplayIdentifier mDefaultDisplayIdentifier; 232 233 // Logical display ids for all displays. 234 @GuardedBy("mLock") 235 private IntArray mDisplayIds = new IntArray(INITIAL_DISPLAYS_SIZE); 236 237 // Flag to disable broadcasting UXR changes - for development purposes 238 @GuardedBy("mLock") 239 private boolean mUxRChangeBroadcastEnabled = true; 240 241 // For dumpsys logging 242 @GuardedBy("mLock") 243 private final LinkedList<TransitionLog> mTransitionLogs = new LinkedList<>(); 244 245 /** 246 * Callback registered with {@link CarOccupantZoneService} for getting display change 247 * notifications and updating UxRs on changed displays. 248 */ 249 private final ICarOccupantZoneCallback mOccupantZoneCallback = 250 new ICarOccupantZoneCallback.Stub() { 251 @Override 252 public void onOccupantZoneConfigChanged(int flags) throws RemoteException { 253 // Respond to display changes. 254 if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) == 0) { 255 return; 256 } 257 258 if (DBG) { 259 String flagString = DebugUtils.flagsToString( 260 CarOccupantZoneManager.class, "ZONE_CONFIG_CHANGE_FLAG_", flags); 261 Slogf.d(TAG, "onOccupantZoneConfigChanged: display zone change flag=%s", 262 flagString); 263 } 264 List<OccupantZoneInfo> occupantZoneInfos = 265 mCarOccupantZoneService.getAllOccupantZones(); 266 IntArray updatedDisplayIds = new IntArray(8); 267 IntArray newlyAddedDisplayIds = new IntArray(8); 268 CarUxRestrictions unrestrictedRestrictions = createUnrestrictedRestrictions(); 269 synchronized (mLock) { 270 for (int i = 0; i < occupantZoneInfos.size(); i++) { 271 OccupantZoneInfo occupantZoneInfo = occupantZoneInfos.get(i); 272 int zoneId = occupantZoneInfo.zoneId; 273 int[] displayIds = 274 mCarOccupantZoneService.getAllDisplaysForOccupantZone( 275 zoneId); 276 for (int j = 0; j < displayIds.length; j++) { 277 int displayId = displayIds[j]; 278 updatedDisplayIds.add(displayId); 279 // Populate UxR only for newly added displays. 280 if (mDisplayIds.indexOf(displayId) != -1) { 281 continue; 282 } 283 newlyAddedDisplayIds.add(displayId); 284 if (DBG) { 285 Slogf.d(TAG, "Populating UxR for display %d", displayId); 286 } 287 // UxR will be updated in initializeUxRestrictions below. 288 mCurrentUxRestrictions.put(displayId, unrestrictedRestrictions); 289 } 290 } 291 // Remove UxR for removed displays from 292 // mCurrentUxRestrictions. 293 for (int i = 0; i < mDisplayIds.size(); i++) { 294 int displayId = mDisplayIds.get(i); 295 if (updatedDisplayIds.indexOf(displayId) == -1) { 296 if (DBG) { 297 Slogf.d(TAG, "Removing UxR for display %d", displayId); 298 } 299 mCurrentUxRestrictions.remove(displayId); 300 } 301 } 302 303 mDisplayIds = updatedDisplayIds; 304 if (DBG) { 305 Slogf.d(TAG, "Display ids are updated to: %s", mDisplayIds); 306 } 307 } 308 309 if (newlyAddedDisplayIds.size() > 0) { 310 // Initialize UxR on newly added displays. 311 initializeUxRestrictions(newlyAddedDisplayIds); 312 } 313 } 314 }; 315 CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService, CarPropertyService propertyService, CarOccupantZoneService carOccupantZoneService)316 public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService, 317 CarPropertyService propertyService, CarOccupantZoneService carOccupantZoneService) { 318 mContext = context; 319 mDisplayManager = mContext.getSystemService(DisplayManager.class); 320 mDrivingStateService = drvService; 321 mCarPropertyService = propertyService; 322 mCarOccupantZoneService = carOccupantZoneService; 323 } 324 325 @Override init()326 public void init() { 327 synchronized (mLock) { 328 initDisplayIdsLocked(); 329 330 // If getCurrentUxRestrictions() is called before init(), null will be returned. 331 mCurrentUxRestrictions = new SparseArray<>(8); 332 333 // Unrestricted until driving state information is received. During boot up, we don't 334 // want everything to be blocked until data is available from CarPropertyManager. 335 // If we start driving and still don't get speed or gear information, 336 // we have bigger problems. 337 CarUxRestrictions unrestrictedRestrictions = createUnrestrictedRestrictions(); 338 for (int i = 0; i < mDisplayIds.size(); i++) { 339 int displayId = mDisplayIds.get(i); 340 mCurrentUxRestrictions.put(displayId, unrestrictedRestrictions); 341 } 342 343 // Load the prod config, or if there is a staged one, promote that first only if the 344 // current driving state, as provided by the driving state service, is parked. 345 mCarUxRestrictionsConfigurations = convertToMapLocked(loadConfig()); 346 } 347 348 // Subscribe to driving state changes. 349 mDrivingStateService.registerDrivingStateChangeListener( 350 mICarDrivingStateChangeEventListener); 351 // Subscribe to property service for speed. 352 mCarPropertyService.registerListenerSafe(VehicleProperty.PERF_VEHICLE_SPEED, 353 PROPERTY_UPDATE_RATE, mICarPropertyEventListener); 354 355 IntArray displayIds; 356 synchronized (mLock) { 357 displayIds = IntArray.fromArray(mDisplayIds.toArray(), mDisplayIds.size()); 358 } 359 initializeUxRestrictions(displayIds); 360 361 // Register callback to respond to display changes and update UxRs on changed displays. 362 mCarOccupantZoneService.registerCallback(mOccupantZoneCallback); 363 } 364 365 @Override getConfigs()366 public List<CarUxRestrictionsConfiguration> getConfigs() { 367 CarServiceUtils.assertPermission(mContext, 368 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 369 synchronized (mLock) { 370 return new ArrayList<>(mCarUxRestrictionsConfigurations.values()); 371 } 372 } 373 374 /** 375 * Loads UX restrictions configurations and returns them. 376 * 377 * <p>Reads config from the following sources in order: 378 * <ol> 379 * <li>saved config set by {@link #saveUxRestrictionsConfigurationForNextBoot(List)}; 380 * <li>XML resource config from {@code R.xml.car_ux_restrictions_map}; 381 * <li>hardcoded default config. 382 * </ol> 383 * 384 * This method attempts to promote staged config file, which requires getting the current 385 * driving state. 386 */ 387 @VisibleForTesting loadConfig()388 List<CarUxRestrictionsConfiguration> loadConfig() { 389 promoteStagedConfig(); 390 List<CarUxRestrictionsConfiguration> configs; 391 392 // Production config, if available, is the first choice. 393 File prodConfig = getFile(CONFIG_FILENAME_PRODUCTION); 394 if (prodConfig.exists()) { 395 logd("Attempting to read production config"); 396 configs = readPersistedConfig(prodConfig); 397 if (configs != null) { 398 return configs; 399 } 400 } 401 402 // XML config is the second choice. 403 logd("Attempting to read config from XML resource"); 404 configs = readXmlConfig(); 405 if (configs != null) { 406 return configs; 407 } 408 409 // This should rarely happen. 410 Slogf.w(TAG, "Creating default config"); 411 412 configs = new ArrayList<>(); 413 synchronized (mLock) { 414 for (int i = 0; i < mDisplayIds.size(); i++) { 415 int displayId = mDisplayIds.get(i); 416 DisplayIdentifier displayIdentifier = getDisplayIdentifier(displayId); 417 if (displayIdentifier == null) { 418 Slogf.e(TAG, "loadConfig: cannot map display id %d to DisplayIdentifier", 419 displayId); 420 continue; 421 } 422 configs.add(createDefaultConfig(displayIdentifier)); 423 } 424 } 425 return configs; 426 } 427 getFile(String filename)428 private File getFile(String filename) { 429 SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class); 430 return new File(systemInterface.getSystemCarDir(), filename); 431 } 432 433 @Nullable readXmlConfig()434 private List<CarUxRestrictionsConfiguration> readXmlConfig() { 435 try { 436 return CarUxRestrictionsConfigurationXmlParser.parse( 437 mContext, R.xml.car_ux_restrictions_map); 438 } catch (IOException | XmlPullParserException e) { 439 Slogf.e(TAG, "Could not read config from XML resource", e); 440 } 441 return null; 442 } 443 444 /** 445 * Promotes the staged config to prod, by replacing the prod file. Only do this if the car is 446 * parked to avoid changing the restrictions during a drive. 447 */ promoteStagedConfig()448 private void promoteStagedConfig() { 449 Path stagedConfig = getFile(CONFIG_FILENAME_STAGED).toPath(); 450 451 CarDrivingStateEvent currentDrivingStateEvent = 452 mDrivingStateService.getCurrentDrivingState(); 453 // Only promote staged config when car is parked. 454 if (currentDrivingStateEvent != null 455 && currentDrivingStateEvent.eventValue == DRIVING_STATE_PARKED 456 && Files.exists(stagedConfig)) { 457 458 Path prod = getFile(CONFIG_FILENAME_PRODUCTION).toPath(); 459 try { 460 logd("Attempting to promote stage config"); 461 Files.move(stagedConfig, prod, REPLACE_EXISTING); 462 } catch (IOException e) { 463 Slogf.e(TAG, "Could not promote state config", e); 464 } 465 } 466 } 467 468 // Update current restrictions on the given displays by getting the current driving state and 469 // speed. initializeUxRestrictions(IntArray displayIds)470 private void initializeUxRestrictions(IntArray displayIds) { 471 CarDrivingStateEvent currentDrivingStateEvent = 472 mDrivingStateService.getCurrentDrivingState(); 473 // if we don't have enough information from the CarPropertyService to compute the UX 474 // restrictions, then leave the UX restrictions unchanged from what it was initialized to. 475 if (currentDrivingStateEvent == null 476 || currentDrivingStateEvent.eventValue == DRIVING_STATE_UNKNOWN) { 477 return; 478 } 479 480 // At this point the underlying CarPropertyService has provided us enough information to 481 // compute the UX restrictions that could be potentially different from the initial UX 482 // restrictions. 483 synchronized (mLock) { 484 handleDrivingStateEventOnDisplaysLocked(currentDrivingStateEvent, displayIds); 485 } 486 } 487 getCurrentSpeed()488 private @FloatRange(from = 0f) Optional<Float> getCurrentSpeed() { 489 CarPropertyValue value = mCarPropertyService.getPropertySafe( 490 VehicleProperty.PERF_VEHICLE_SPEED, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 491 if (value != null && value.getStatus() == CarPropertyValue.STATUS_AVAILABLE) { 492 return Optional.of(Math.abs((Float) value.getValue())); 493 } 494 return Optional.empty(); 495 } 496 497 @Override release()498 public void release() { 499 while (mUxRClients.getRegisteredCallbackCount() > 0) { 500 for (int i = mUxRClients.getRegisteredCallbackCount() - 1; i >= 0; i--) { 501 ICarUxRestrictionsChangeListener client = mUxRClients.getRegisteredCallbackItem(i); 502 if (client == null) { 503 continue; 504 } 505 mUxRClients.unregister(client); 506 } 507 } 508 mDrivingStateService.unregisterDrivingStateChangeListener( 509 mICarDrivingStateChangeEventListener); 510 } 511 512 // Binder methods 513 514 /** 515 * Registers a {@link ICarUxRestrictionsChangeListener} to be notified for changes to the UX 516 * restrictions. 517 * 518 * @param listener Listener to register 519 * @param displayId UX restrictions on this display will be notified. 520 */ 521 @Override registerUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener, int displayId)522 public void registerUxRestrictionsChangeListener( 523 ICarUxRestrictionsChangeListener listener, int displayId) { 524 if (listener == null) { 525 Slogf.e(TAG, "registerUxRestrictionsChangeListener(): listener null"); 526 throw new IllegalArgumentException("Listener is null"); 527 } 528 mUxRClients.register(listener, new RemoteCallbackListCookie(displayId)); 529 } 530 531 /** 532 * Unregister the given UX Restrictions listener 533 * 534 * @param listener client to unregister 535 */ 536 @Override unregisterUxRestrictionsChangeListener(ICarUxRestrictionsChangeListener listener)537 public void unregisterUxRestrictionsChangeListener(ICarUxRestrictionsChangeListener listener) { 538 if (listener == null) { 539 Slogf.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null"); 540 throw new IllegalArgumentException("Listener is null"); 541 } 542 543 mUxRClients.unregister(listener); 544 } 545 546 /** 547 * Gets the current UX restrictions for a display. 548 * 549 * @param displayId UX restrictions on this display will be returned. 550 */ 551 @Nullable 552 @Override getCurrentUxRestrictions(int displayId)553 public CarUxRestrictions getCurrentUxRestrictions(int displayId) { 554 CarUxRestrictions restrictions = null; 555 if (DBG) { 556 Slogf.d(TAG, "getCurrentUxRestrictions: display id %d", displayId); 557 } 558 synchronized (mLock) { 559 if (mCurrentUxRestrictions == null) { 560 Slogf.wtf(TAG, "getCurrentUxRestrictions() called before init()"); 561 return null; 562 } 563 564 if (isDebugBuild() && !mUxRChangeBroadcastEnabled) { 565 Slogf.d(TAG, "Returning unrestricted UX Restriction due to setting"); 566 return createUnrestrictedRestrictions(); 567 } 568 restrictions = mCurrentUxRestrictions.get(displayId); 569 } 570 571 if (restrictions == null) { 572 Slogf.e(TAG, "Cannot find restrictions for displayId: %d" 573 + ". Returning full restrictions.", displayId); 574 restrictions = createFullyRestrictedRestrictions(); 575 } 576 return restrictions; 577 } 578 579 /** 580 * Convenience method to retrieve restrictions for default display. 581 */ 582 @Nullable getCurrentUxRestrictions()583 public CarUxRestrictions getCurrentUxRestrictions() { 584 return getCurrentUxRestrictions(Display.DEFAULT_DISPLAY); 585 } 586 587 @Override saveUxRestrictionsConfigurationForNextBoot( List<CarUxRestrictionsConfiguration> configs)588 public boolean saveUxRestrictionsConfigurationForNextBoot( 589 List<CarUxRestrictionsConfiguration> configs) { 590 CarServiceUtils.assertPermission(mContext, 591 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 592 593 validateConfigs(configs); 594 595 return persistConfig(configs, CONFIG_FILENAME_STAGED); 596 } 597 598 @Override 599 @Nullable getStagedConfigs()600 public List<CarUxRestrictionsConfiguration> getStagedConfigs() { 601 CarServiceUtils.assertPermission(mContext, 602 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 603 604 File stagedConfig = getFile(CONFIG_FILENAME_STAGED); 605 if (stagedConfig.exists()) { 606 logd("Attempting to read staged config"); 607 return readPersistedConfig(stagedConfig); 608 } else { 609 return null; 610 } 611 } 612 613 /** 614 * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to 615 * be applied in the same driving state. Restrictions for each mode can be configured through 616 * {@link CarUxRestrictionsConfiguration}. 617 * 618 * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}. 619 * 620 * @param mode the restriction mode 621 * @return {@code true} if mode was successfully changed; {@code false} otherwise. 622 * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions 623 * @see CarUxRestrictionsConfiguration.Builder 624 */ 625 @Override setRestrictionMode(@onNull String mode)626 public boolean setRestrictionMode(@NonNull String mode) { 627 CarServiceUtils.assertPermission(mContext, 628 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 629 Objects.requireNonNull(mode, "mode must not be null"); 630 631 synchronized (mLock) { 632 if (mRestrictionMode.equals(mode)) { 633 return true; 634 } 635 636 addTransitionLogLocked(TAG, mRestrictionMode, mode, System.currentTimeMillis(), 637 "Restriction mode"); 638 mRestrictionMode = mode; 639 logd("Set restriction mode to: " + mode); 640 641 handleDrivingStateEventLocked(mDrivingStateService.getCurrentDrivingState()); 642 } 643 return true; 644 } 645 646 @Override 647 @NonNull getRestrictionMode()648 public String getRestrictionMode() { 649 CarServiceUtils.assertPermission(mContext, 650 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 651 652 synchronized (mLock) { 653 return mRestrictionMode; 654 } 655 } 656 657 /** 658 * Returns all supported restriction modes 659 */ 660 @NonNull getSupportedRestrictionModes()661 public List<String> getSupportedRestrictionModes() { 662 Set<String> modes = new HashSet<>(); 663 Collection<CarUxRestrictionsConfiguration> configs; 664 synchronized (mLock) { 665 configs = mCarUxRestrictionsConfigurations.values(); 666 } 667 668 for (CarUxRestrictionsConfiguration config : configs) { 669 modes.addAll(config.getSupportedRestrictionModes()); 670 } 671 672 return new ArrayList<>(modes); 673 } 674 675 /** 676 * Writes configuration into the specified file. 677 * 678 * IO access on file is not thread safe. Caller should ensure threading protection. 679 */ persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename)680 private boolean persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename) { 681 File file = getFile(filename); 682 AtomicFile stagedFile = new AtomicFile(file); 683 FileOutputStream fos; 684 try { 685 fos = stagedFile.startWrite(); 686 } catch (IOException e) { 687 Slogf.e(TAG, "Could not open file to persist config", e); 688 return false; 689 } 690 try (JsonWriter jsonWriter = new JsonWriter( 691 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) { 692 writeJson(jsonWriter, configs); 693 } catch (IOException e) { 694 Slogf.e(TAG, "Could not persist config", e); 695 stagedFile.failWrite(fos); 696 return false; 697 } 698 stagedFile.finishWrite(fos); 699 return true; 700 } 701 702 @VisibleForTesting writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs)703 void writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs) 704 throws IOException { 705 jsonWriter.beginObject(); 706 jsonWriter.name(JSON_NAME_SCHEMA_VERSION).value(JSON_SCHEMA_VERSION_V2); 707 jsonWriter.name(JSON_NAME_RESTRICTIONS); 708 jsonWriter.beginArray(); 709 for (int i = 0; i < configs.size(); i++) { 710 CarUxRestrictionsConfiguration config = configs.get(i); 711 config.writeJson(jsonWriter); 712 } 713 jsonWriter.endArray(); 714 jsonWriter.endObject(); 715 } 716 717 @Nullable readPersistedConfig(File file)718 private List<CarUxRestrictionsConfiguration> readPersistedConfig(File file) { 719 if (!file.exists()) { 720 Slogf.e(TAG, "Could not find config file: " + file.getName()); 721 return null; 722 } 723 724 // Take one pass at the file to check the version and then a second pass to read the 725 // contents. We could assess the version and read in one pass, but we're preferring 726 // clarity over complexity here. 727 int schemaVersion = readFileSchemaVersion(file); 728 729 AtomicFile configFile = new AtomicFile(file); 730 try (JsonReader reader = new JsonReader( 731 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) { 732 List<CarUxRestrictionsConfiguration> configs = new ArrayList<>(); 733 switch (schemaVersion) { 734 case JSON_SCHEMA_VERSION_V1: 735 readV1Json(reader, configs); 736 break; 737 case JSON_SCHEMA_VERSION_V2: 738 readV2Json(reader, configs); 739 break; 740 default: 741 Slogf.e(TAG, "Unable to parse schema for version " + schemaVersion); 742 } 743 744 return configs; 745 } catch (IOException e) { 746 Slogf.e(TAG, "Could not read persisted config file " + file.getName(), e); 747 } 748 return null; 749 } 750 readV1Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)751 private void readV1Json(JsonReader reader, 752 List<CarUxRestrictionsConfiguration> configs) throws IOException { 753 readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V1); 754 } 755 readV2Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)756 private void readV2Json(JsonReader reader, 757 List<CarUxRestrictionsConfiguration> configs) throws IOException { 758 reader.beginObject(); 759 while (reader.hasNext()) { 760 String name = reader.nextName(); 761 switch (name) { 762 case JSON_NAME_RESTRICTIONS: 763 readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V2); 764 break; 765 default: 766 reader.skipValue(); 767 } 768 } 769 reader.endObject(); 770 } 771 readFileSchemaVersion(File file)772 private int readFileSchemaVersion(File file) { 773 AtomicFile configFile = new AtomicFile(file); 774 try (JsonReader reader = new JsonReader( 775 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) { 776 if (reader.peek() == JsonToken.BEGIN_ARRAY) { 777 // only schema V1 beings with an array - no need to keep reading 778 reader.close(); 779 return JSON_SCHEMA_VERSION_V1; 780 } else { 781 reader.beginObject(); 782 while (reader.hasNext()) { 783 String name = reader.nextName(); 784 switch (name) { 785 case JSON_NAME_SCHEMA_VERSION: 786 int schemaVersion = reader.nextInt(); 787 // got the version, no need to continue reading 788 reader.close(); 789 return schemaVersion; 790 default: 791 reader.skipValue(); 792 } 793 } 794 reader.endObject(); 795 } 796 } catch (IOException e) { 797 Slogf.e(TAG, "Could not read persisted config file " + file.getName(), e); 798 } 799 return UNKNOWN_JSON_SCHEMA_VERSION; 800 } 801 readRestrictionsArray(JsonReader reader, List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)802 private void readRestrictionsArray(JsonReader reader, 803 List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion) 804 throws IOException { 805 reader.beginArray(); 806 while (reader.hasNext()) { 807 configs.add(CarUxRestrictionsConfiguration.readJson(reader, schemaVersion)); 808 } 809 reader.endArray(); 810 } 811 812 /** 813 * Enable/disable UX restrictions change broadcast blocking. 814 * Setting this to true will stop broadcasts of UX restriction change to listeners. 815 * This method works only on debug builds and the caller of this method needs to have the same 816 * signature of the car service. 817 */ setUxRChangeBroadcastEnabled(boolean enable)818 public void setUxRChangeBroadcastEnabled(boolean enable) { 819 if (!isDebugBuild()) { 820 Slogf.e(TAG, "Cannot set UX restriction change broadcast."); 821 return; 822 } 823 // Check if the caller has the same signature as that of the car service. 824 if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid()) 825 != PackageManager.SIGNATURE_MATCH) { 826 throw new SecurityException( 827 "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid()) 828 + " does not have the right signature"); 829 } 830 831 synchronized (mLock) { 832 if (enable) { 833 // if enabling it back, send the current restrictions 834 mUxRChangeBroadcastEnabled = true; 835 handleDrivingStateEventLocked( 836 mDrivingStateService.getCurrentDrivingState()); 837 } else { 838 // fake parked state, so if the system is currently restricted, the restrictions are 839 // relaxed. 840 handleDispatchUxRestrictionsLocked(DRIVING_STATE_PARKED, /* speed= */ 0f, 841 mDisplayIds); 842 mUxRChangeBroadcastEnabled = false; 843 } 844 } 845 } 846 isDebugBuild()847 private boolean isDebugBuild() { 848 return BuildHelper.isUserDebugBuild() || BuildHelper.isEngBuild(); 849 } 850 851 @Override 852 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)853 public void dump(IndentingPrintWriter writer) { 854 synchronized (mLock) { 855 writer.println("*CarUxRestrictionsManagerService*"); 856 857 writer.println("UX Restrictions Clients:"); 858 writer.increaseIndent(); 859 BinderHelper.dumpRemoteCallbackList(mUxRClients, writer); 860 writer.decreaseIndent(); 861 for (int i = 0; i < mCurrentUxRestrictions.size(); i++) { 862 writer.printf("Display id: %d UXR: %s\n", mCurrentUxRestrictions.keyAt(i), 863 mCurrentUxRestrictions.valueAt(i)); 864 } 865 if (isDebugBuild()) { 866 writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled); 867 } 868 writer.println("UX Restriction configurations:"); 869 for (CarUxRestrictionsConfiguration config : 870 mCarUxRestrictionsConfigurations.values()) { 871 config.dump(writer); 872 } 873 writer.println("UX Restriction change log:"); 874 for (TransitionLog tlog : mTransitionLogs) { 875 writer.println(tlog); 876 } 877 } 878 } 879 880 @Override 881 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)882 public void dumpProto(ProtoOutputStream proto) {} 883 884 /** 885 * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService} 886 * for getting driving state change notifications. 887 */ 888 private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener = 889 new ICarDrivingStateChangeListener.Stub() { 890 @Override 891 public void onDrivingStateChanged(CarDrivingStateEvent event) { 892 logd("Driving State Changed:" + event.eventValue); 893 synchronized (mLock) { 894 handleDrivingStateEventLocked(event); 895 } 896 } 897 }; 898 899 /** 900 * Handles the driving state change events coming from the {@link CarDrivingStateService} on 901 * the given displays. 902 * <p>Maps the driving state to the corresponding UX Restrictions and dispatch the 903 * UX Restriction change to the registered clients. 904 */ 905 @GuardedBy("mLock") handleDrivingStateEventOnDisplaysLocked(CarDrivingStateEvent event, IntArray displayIds)906 void handleDrivingStateEventOnDisplaysLocked(CarDrivingStateEvent event, 907 IntArray displayIds) { 908 if (event == null) { 909 return; 910 } 911 int drivingState = event.eventValue; 912 Optional<Float> currentSpeed = getCurrentSpeed(); 913 914 if (currentSpeed.isPresent()) { 915 mCurrentMovingSpeed = currentSpeed.get(); 916 handleDispatchUxRestrictionsLocked(drivingState, mCurrentMovingSpeed, displayIds); 917 } else if (drivingState != DRIVING_STATE_MOVING) { 918 // If speed is unavailable, but the driving state is parked or unknown, it can still be 919 // handled. 920 logd("Speed null when driving state is: " + drivingState); 921 handleDispatchUxRestrictionsLocked(drivingState, /* speed= */ 0f, displayIds); 922 } else { 923 // If we get here, it means the car is moving while the speed is unavailable. 924 // This only happens in the case of a fault. We should take the safest route by assuming 925 // the car is moving at a speed in the highest speed range. 926 Slogf.e(TAG, "Unexpected: Speed null when driving state is: " + drivingState); 927 logd("Treating speed null when driving state is: " + drivingState 928 + " as in highest speed range"); 929 handleDispatchUxRestrictionsLocked(DRIVING_STATE_MOVING, /* speed= */ MAX_SPEED, 930 displayIds); 931 } 932 } 933 934 /** 935 * Handles the driving state change events coming from the {@link CarDrivingStateService} on 936 * all displays. 937 * 938 * <p>Maps the driving state to the corresponding UX Restrictions and dispatch the 939 * UX Restriction change to the registered clients. 940 */ 941 @VisibleForTesting 942 @GuardedBy("mLock") handleDrivingStateEventLocked(CarDrivingStateEvent event)943 void handleDrivingStateEventLocked(CarDrivingStateEvent event) { 944 handleDrivingStateEventOnDisplaysLocked(event, mDisplayIds); 945 } 946 947 /** 948 * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting 949 * speed change notifications. 950 */ 951 private final ICarPropertyEventListener mICarPropertyEventListener = 952 new ICarPropertyEventListener.Stub() { 953 @Override 954 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 955 synchronized (mLock) { 956 for (int i = 0; i < events.size(); i++) { 957 CarPropertyEvent event = events.get(i); 958 if ((event.getEventType() 959 == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) 960 && (event.getCarPropertyValue().getPropertyId() 961 == VehicleProperty.PERF_VEHICLE_SPEED)) { 962 handleSpeedChangeLocked( 963 Math.abs((Float) event.getCarPropertyValue().getValue())); 964 } 965 } 966 } 967 } 968 }; 969 970 @GuardedBy("mLock") handleSpeedChangeLocked(@loatRangefrom = 0f) float newSpeed)971 private void handleSpeedChangeLocked(@FloatRange(from = 0f) float newSpeed) { 972 if (newSpeed == mCurrentMovingSpeed) { 973 // Ignore if speed hasn't changed 974 return; 975 } 976 mCurrentMovingSpeed = newSpeed; 977 int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue; 978 if (currentDrivingState != DRIVING_STATE_MOVING) { 979 // Ignore speed changes if the vehicle is not moving 980 return; 981 } 982 handleDispatchUxRestrictionsLocked(currentDrivingState, mCurrentMovingSpeed, mDisplayIds); 983 } 984 985 /** 986 * Handle dispatching UX restrictions change on a set of display ids. 987 * 988 * <p> This method also handles the special case when the car is moving but its speed 989 * is unavailable in which case highest speed will be assumed. 990 * 991 * @param currentDrivingState driving state of the vehicle 992 * @param speed speed of the vehicle 993 * @param displayIds Ids of displays on which to dispatch Ux restrictions change 994 */ 995 @GuardedBy("mLock") handleDispatchUxRestrictionsLocked(@arDrivingState int currentDrivingState, @FloatRange(from = 0f) float speed, IntArray displayIds)996 private void handleDispatchUxRestrictionsLocked(@CarDrivingState int currentDrivingState, 997 @FloatRange(from = 0f) float speed, IntArray displayIds) { 998 Objects.requireNonNull(mCarUxRestrictionsConfigurations, 999 "mCarUxRestrictionsConfigurations must be initialized"); 1000 Objects.requireNonNull(mCurrentUxRestrictions, 1001 "mCurrentUxRestrictions must be initialized"); 1002 1003 if (isDebugBuild() && !mUxRChangeBroadcastEnabled) { 1004 Slogf.d(TAG, "Not dispatching UX Restriction due to setting"); 1005 return; 1006 } 1007 1008 // keep track of only those changed UxR for dispatching. 1009 SparseArray<CarUxRestrictions> updatedUxRestrictions = new SparseArray<>( 1010 INITIAL_DISPLAYS_SIZE); 1011 IntArray displaysToDispatch = new IntArray(INITIAL_DISPLAYS_SIZE); 1012 for (int i = 0; i < displayIds.size(); i++) { 1013 int displayId = displayIds.get(i); 1014 if (DBG) { 1015 Slogf.d(TAG, 1016 "handleDispatchUxRestrictionsLocked: Recalculating UxR for display %d...", 1017 displayId); 1018 } 1019 1020 CarUxRestrictions uxRestrictions; 1021 // Map logical display to DisplayIdentifier to get UxR from config based on 1022 // DisplayIdentifier. 1023 DisplayIdentifier displayIdentifier = getDisplayIdentifier(displayId); 1024 if (displayIdentifier == null) { 1025 Slogf.w(TAG, 1026 "handleDispatchUxRestrictionsLocked: cannot map display id %d to" 1027 + " DisplayIdentifier based on which to get UxR from config," 1028 + " defaulting to fully restricted restrictions", 1029 displayId); 1030 uxRestrictions = createFullyRestrictedRestrictions(); 1031 } else { 1032 if (DBG) { 1033 Slogf.d(TAG, 1034 "handleDispatchUxRestrictionsLocked: mapped display id %d to" 1035 + " DisplayIdentifier %s", displayId, displayIdentifier); 1036 } 1037 CarUxRestrictionsConfiguration config = mCarUxRestrictionsConfigurations.get( 1038 displayIdentifier); 1039 if (config == null) { 1040 Slogf.w(TAG, 1041 "handleDispatchUxRestrictionsLocked: cannot find UxR" 1042 + " DisplayIdentifier %s from config, defaulting to fully restricted" 1043 + " restrictions", displayIdentifier); 1044 // If UxR config is not found for a physical port, assume it's fully restricted. 1045 uxRestrictions = createFullyRestrictedRestrictions(); 1046 } else { 1047 uxRestrictions = config.getUxRestrictions( 1048 currentDrivingState, speed, mRestrictionMode); 1049 } 1050 } 1051 1052 if (!mCurrentUxRestrictions.contains(displayId)) { 1053 // This should never happen. 1054 Slogf.wtf(TAG, "handleDispatchUxRestrictionsLocked: Unrecognized display %d:" 1055 + " in new UxR", displayId); 1056 continue; 1057 } 1058 CarUxRestrictions currentUxRestrictions = mCurrentUxRestrictions.get(displayId); 1059 if (DBG) { 1060 Slogf.d(TAG, "Display id %d\tDO old->new: %b -> %b", displayId, 1061 currentUxRestrictions.isRequiresDistractionOptimization(), 1062 uxRestrictions.isRequiresDistractionOptimization()); 1063 Slogf.d(TAG, "Display id %d\tUxR old->new: 0x%x -> 0x%x", displayId, 1064 currentUxRestrictions.getActiveRestrictions(), 1065 uxRestrictions.getActiveRestrictions()); 1066 } 1067 if (!currentUxRestrictions.isSameRestrictions(uxRestrictions)) { 1068 if (DBG) { 1069 Slogf.d(TAG, "Updating UxR on display %d", displayId); 1070 } 1071 // Update UxR for displayId in place. 1072 mCurrentUxRestrictions.put(displayId, uxRestrictions); 1073 // Ignore dispatching if the restrictions have not changed. 1074 displaysToDispatch.add(displayId); 1075 updatedUxRestrictions.put(displayId, uxRestrictions); 1076 addTransitionLogLocked(currentUxRestrictions, uxRestrictions); 1077 } 1078 } 1079 1080 if (displaysToDispatch.size() != 0) { 1081 dispatchRestrictionsToClientsLocked(updatedUxRestrictions, displaysToDispatch); 1082 } 1083 } 1084 dispatchRestrictionsToClientsLocked( SparseArray<CarUxRestrictions> updatedUxRestrictions, IntArray displaysToDispatch)1085 private void dispatchRestrictionsToClientsLocked( 1086 SparseArray<CarUxRestrictions> updatedUxRestrictions, 1087 IntArray displaysToDispatch) { 1088 logd("dispatching to clients"); 1089 boolean success = mClientDispatchHandler.post(() -> { 1090 int numClients = mUxRClients.beginBroadcast(); 1091 for (int i = 0; i < numClients; i++) { 1092 ICarUxRestrictionsChangeListener callback = mUxRClients.getBroadcastItem(i); 1093 RemoteCallbackListCookie cookie = 1094 (RemoteCallbackListCookie) mUxRClients.getBroadcastCookie(i); 1095 if (displaysToDispatch.indexOf(cookie.mDisplayId) == -1) { 1096 continue; 1097 } 1098 CarUxRestrictions restrictions = updatedUxRestrictions.get(cookie.mDisplayId); 1099 if (restrictions == null) { 1100 // Don't dispatch to displays without configurations 1101 Slogf.w(TAG, "Can't find UxR on display %d for dispatching", 1102 cookie.mDisplayId); 1103 continue; 1104 } 1105 if (DBG) { 1106 Slogf.d(TAG, "Dispatching UxR change %s to display %d", restrictions, 1107 cookie.mDisplayId); 1108 } 1109 try { 1110 callback.onUxRestrictionsChanged(restrictions); 1111 } catch (RemoteException e) { 1112 Slogf.e(TAG, "Dispatch to listener %s failed for restrictions (%s)", callback, 1113 restrictions); 1114 } 1115 } 1116 mUxRClients.finishBroadcast(); 1117 }); 1118 1119 if (!success) { 1120 Slogf.e(TAG, "Failed to post Ux Restrictions changes event to dispatch handler"); 1121 } 1122 } 1123 1124 @GuardedBy("mLock") initDisplayIdsLocked()1125 private void initDisplayIdsLocked() { 1126 // Populate logical display ids of all displays in all occupant zones. 1127 List<CarOccupantZoneManager.OccupantZoneInfo> occupantZoneInfos = 1128 mCarOccupantZoneService.getAllOccupantZones(); 1129 for (int i = 0; i < occupantZoneInfos.size(); i++) { 1130 CarOccupantZoneManager.OccupantZoneInfo occupantZoneInfo = occupantZoneInfos.get(i); 1131 int zoneId = occupantZoneInfo.zoneId; 1132 int[] displayIds = mCarOccupantZoneService.getAllDisplaysForOccupantZone(zoneId); 1133 for (int j = 0; j < displayIds.length; j++) { 1134 int displayId = displayIds[j]; 1135 Slogf.i(TAG, "initDisplayIdsLocked: adding display: %d", displayId); 1136 mDisplayIds.add(displayId); 1137 } 1138 } 1139 1140 // Find the default display id by zone from driver main display. 1141 // This will be used when UxR config does not specify display id. 1142 IntArray displayIds = mCarOccupantZoneService.getAllDisplayIdsForDriver(DISPLAY_TYPE_MAIN); 1143 if (DBG) { 1144 Slogf.d(TAG, "Driver displayIds: " + Arrays.toString(displayIds.toArray())); 1145 } 1146 if (displayIds.size() > 0) { 1147 int driverMainDisplayId = displayIds.get(0); 1148 DisplayIdentifier displayIdentifier = getDisplayIdentifier(driverMainDisplayId); 1149 if (displayIdentifier == null) { 1150 Slogf.w(TAG, "initDisplayIdsLocked: cannot map driver main display id %d" 1151 + " to DisplayIdentifier", driverMainDisplayId); 1152 displayIdentifier = new DisplayIdentifier( 1153 CarOccupantZoneManager.OccupantZoneInfo.INVALID_ZONE_ID, 1154 CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN); 1155 } 1156 // The first display id from the driver displays will be the default display id. 1157 Slogf.i(TAG, "initDisplayIdsLocked: setting default display id by zone to %s", 1158 displayIdentifier); 1159 mDefaultDisplayIdentifier = displayIdentifier; 1160 } else { 1161 Slogf.w(TAG, "initDisplayIdsLocked: driver main display not found"); 1162 mDefaultDisplayIdentifier = new DisplayIdentifier( 1163 CarOccupantZoneManager.OccupantZoneInfo.INVALID_ZONE_ID, 1164 CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN); 1165 } 1166 } 1167 1168 @Nullable getDisplayIdentifier(int displayId)1169 private DisplayIdentifier getDisplayIdentifier(int displayId) { 1170 CarOccupantZoneService.DisplayConfig displayConfig = 1171 mCarOccupantZoneService.findDisplayConfigForDisplayId(displayId); 1172 if (displayConfig == null) return null; 1173 DisplayIdentifier displayIdentifier = new DisplayIdentifier( 1174 displayConfig.occupantZoneId, displayConfig.displayType); 1175 return displayIdentifier; 1176 } 1177 1178 @Nullable getDisplayIdentifierFromPort(int port)1179 private DisplayIdentifier getDisplayIdentifierFromPort(int port) { 1180 CarOccupantZoneService.DisplayConfig displayConfig = 1181 mCarOccupantZoneService.findDisplayConfigForPort(port); 1182 if (displayConfig == null) return null; 1183 DisplayIdentifier displayIdentifier = new DisplayIdentifier( 1184 displayConfig.occupantZoneId, displayConfig.displayType); 1185 return displayIdentifier; 1186 } 1187 1188 @GuardedBy("mLock") convertToMapLocked( List<CarUxRestrictionsConfiguration> configs)1189 private Map<DisplayIdentifier, CarUxRestrictionsConfiguration> convertToMapLocked( 1190 List<CarUxRestrictionsConfiguration> configs) { 1191 validateConfigs(configs); 1192 1193 Map<DisplayIdentifier, CarUxRestrictionsConfiguration> result = new ArrayMap<>(); 1194 1195 if (configs.size() == 1) { 1196 CarUxRestrictionsConfiguration config = configs.get(0); 1197 if (config.getPhysicalPort() == null 1198 && config.getOccupantZoneId() == OccupantZoneInfo.INVALID_ZONE_ID) { 1199 // If no display is specified, the default from driver's main display 1200 // is assumed. 1201 result.put(mDefaultDisplayIdentifier, config); 1202 return result; 1203 } 1204 } 1205 1206 for (int i = 0; i < configs.size(); i++) { 1207 CarUxRestrictionsConfiguration config = configs.get(i); 1208 DisplayIdentifier displayIdentifier; 1209 // A display is specified either by physical port or the combination of occupant zone id 1210 // and display type. 1211 // Note: physical port and the combination of occupant zone id and display type won't 1212 // coexist. This has been checked when parsing the UxR config. 1213 if (config.getPhysicalPort() != null) { 1214 displayIdentifier = getDisplayIdentifierFromPort(config.getPhysicalPort()); 1215 if (displayIdentifier != null) { 1216 if (DBG) { 1217 Slogf.d(TAG, 1218 "convertToMapLocked: port %d is mapped to DisplayIdentifier %s", 1219 config.getPhysicalPort(), displayIdentifier); 1220 } 1221 } else { 1222 Slogf.w(TAG, 1223 "convertToMapLocked: port %d can't be mapped to DisplayIdentifier", 1224 config.getPhysicalPort()); 1225 continue; 1226 } 1227 } else { 1228 displayIdentifier = new DisplayIdentifier( 1229 config.getOccupantZoneId(), config.getDisplayType()); 1230 } 1231 result.put(displayIdentifier, config); 1232 } 1233 return result; 1234 } 1235 1236 /** 1237 * Validates configs for multi-display: 1238 * <p><ol> 1239 * <li> Each sets either display port or (occupant zone id, display type); 1240 * <li> Display port is unique; 1241 * <li> The combination of (occupant zone id, display type) is unique. 1242 * </ol> 1243 */ 1244 @VisibleForTesting validateConfigs(List<CarUxRestrictionsConfiguration> configs)1245 void validateConfigs(List<CarUxRestrictionsConfiguration> configs) { 1246 if (configs.size() == 0) { 1247 throw new IllegalArgumentException("Empty configuration."); 1248 } 1249 1250 if (configs.size() == 1) { 1251 return; 1252 } 1253 1254 Set<Integer> existingPorts = new ArraySet<>(configs.size()); 1255 Set<DisplayIdentifier> existingDisplayIdentifiers = new ArraySet<>(configs.size()); 1256 for (int i = 0; i < configs.size(); i++) { 1257 CarUxRestrictionsConfiguration config = configs.get(i); 1258 Integer port = config.getPhysicalPort(); 1259 int occupantZoneId = config.getOccupantZoneId(); 1260 if (port == null && occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 1261 // Size was checked above; safe to assume there are multiple configs. 1262 throw new IllegalArgumentException( 1263 "Input contains multiple configurations; " 1264 + "each must set physical port or the combination of occupant zone id " 1265 + "and display type"); 1266 } 1267 1268 if (port != null) { 1269 if (!existingPorts.add(port)) { 1270 throw new IllegalStateException("Multiple configurations for port " + port); 1271 } 1272 } else { 1273 // TODO(b/241589812): Validate occupant zone. 1274 DisplayIdentifier displayIdentifier = new DisplayIdentifier( 1275 occupantZoneId, config.getDisplayType()); 1276 if (!existingDisplayIdentifiers.add(displayIdentifier)) { 1277 throw new IllegalStateException( 1278 "Multiple configurations for " + displayIdentifier.toString()); 1279 } 1280 } 1281 } 1282 } 1283 createUnrestrictedRestrictions()1284 private CarUxRestrictions createUnrestrictedRestrictions() { 1285 return new CarUxRestrictions.Builder(/* reqOpt= */ false, 1286 CarUxRestrictions.UX_RESTRICTIONS_BASELINE, SystemClock.elapsedRealtimeNanos()) 1287 .build(); 1288 } 1289 createFullyRestrictedRestrictions()1290 private CarUxRestrictions createFullyRestrictedRestrictions() { 1291 return new CarUxRestrictions.Builder( 1292 /*reqOpt= */ true, 1293 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED, 1294 SystemClock.elapsedRealtimeNanos()).build(); 1295 } 1296 createDefaultConfig(DisplayIdentifier displayIdentifier)1297 CarUxRestrictionsConfiguration createDefaultConfig(DisplayIdentifier displayIdentifier) { 1298 return new CarUxRestrictionsConfiguration.Builder() 1299 .setOccupantZoneId(displayIdentifier.occupantZoneId) 1300 .setDisplayType(displayIdentifier.displayType) 1301 .setUxRestrictions(DRIVING_STATE_PARKED, 1302 false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE) 1303 .setUxRestrictions(DRIVING_STATE_IDLING, 1304 false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE) 1305 .setUxRestrictions(DRIVING_STATE_MOVING, 1306 true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED) 1307 .setUxRestrictions(DRIVING_STATE_UNKNOWN, 1308 true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED) 1309 .build(); 1310 } 1311 1312 @GuardedBy("mLock") addTransitionLogLocked(String name, String from, String to, long timestamp, String extra)1313 private void addTransitionLogLocked(String name, String from, String to, long timestamp, 1314 String extra) { 1315 if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) { 1316 mTransitionLogs.remove(); 1317 } 1318 1319 TransitionLog tLog = new TransitionLog(name, from, to, timestamp, extra); 1320 mTransitionLogs.add(tLog); 1321 } 1322 1323 @GuardedBy("mLock") addTransitionLogLocked( CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions)1324 private void addTransitionLogLocked( 1325 CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions) { 1326 if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) { 1327 mTransitionLogs.remove(); 1328 } 1329 StringBuilder extra = new StringBuilder(); 1330 extra.append(oldRestrictions.isRequiresDistractionOptimization() ? "DO -> " : "No DO -> "); 1331 extra.append(newRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO"); 1332 1333 TransitionLog tLog = new TransitionLog(TAG, 1334 oldRestrictions.getActiveRestrictions(), newRestrictions.getActiveRestrictions(), 1335 System.currentTimeMillis(), extra.toString()); 1336 mTransitionLogs.add(tLog); 1337 } 1338 logd(String msg)1339 private static void logd(String msg) { 1340 if (DBG) { 1341 Slogf.d(TAG, msg); 1342 } 1343 } 1344 } 1345