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