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.IndentingPrintWriter; 58 import android.util.JsonReader; 59 import android.util.JsonToken; 60 import android.util.JsonWriter; 61 import android.util.Log; 62 import android.util.Slog; 63 import android.util.SparseArray; 64 import android.view.Display; 65 import android.view.DisplayAddress; 66 67 import com.android.car.systeminterface.SystemInterface; 68 import com.android.internal.annotations.GuardedBy; 69 import com.android.internal.annotations.VisibleForTesting; 70 71 import org.xmlpull.v1.XmlPullParserException; 72 73 import java.io.File; 74 import java.io.FileOutputStream; 75 import java.io.IOException; 76 import java.io.InputStreamReader; 77 import java.io.OutputStreamWriter; 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 = CarLog.tagFor(CarUxRestrictionsManagerService.class); 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 int 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 Integer mPhysicalPort; 146 RemoteCallbackListCookie(Integer physicalPort)147 RemoteCallbackListCookie(Integer 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 an int that represents a physical 156 * port. It includes mappings for virtual displays. 157 */ 158 @GuardedBy("mLock") 159 private final Map<Integer, Integer> mPortLookup = new HashMap<>(); 160 161 @GuardedBy("mLock") 162 private Map<Integer, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations; 163 164 @GuardedBy("mLock") 165 private Map<Integer, CarUxRestrictions> mCurrentUxRestrictions; 166 167 @GuardedBy("mLock") 168 private String mRestrictionMode = UX_RESTRICTION_MODE_BASELINE; 169 170 @GuardedBy("mLock") 171 private float mCurrentMovingSpeed; 172 173 // Represents a physical port for display. 174 @GuardedBy("mLock") 175 private int mDefaultDisplayPhysicalPort; 176 177 @GuardedBy("mLock") 178 private final List<Integer> 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 (int 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 Slog.w(TAG, "Creating default config"); 272 273 configs = new ArrayList<>(); 274 synchronized (mLock) { 275 for (int 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 Slog.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 Slog.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.getPropertySafe( 348 VehicleProperty.PERF_VEHICLE_SPEED, 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 Slog.e(TAG, "registerUxRestrictionsChangeListener(): listener null"); 387 throw new IllegalArgumentException("Listener is null"); 388 } 389 Integer 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 Slog.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 if (mCurrentUxRestrictions == null) { 424 Slog.wtf(TAG, "getCurrentUxRestrictions() called before init()"); 425 return null; 426 } 427 restrictions = mCurrentUxRestrictions.get(getPhysicalPortLocked(displayId)); 428 } 429 if (restrictions == null) { 430 Slog.e(TAG, "Restrictions are null for displayId:" + displayId 431 + ". Returning full restrictions."); 432 restrictions = createFullyRestrictedRestrictions(); 433 } 434 return restrictions; 435 } 436 437 /** 438 * Convenience method to retrieve restrictions for default display. 439 */ 440 @Nullable getCurrentUxRestrictions()441 public CarUxRestrictions getCurrentUxRestrictions() { 442 return getCurrentUxRestrictions(Display.DEFAULT_DISPLAY); 443 } 444 445 @Override saveUxRestrictionsConfigurationForNextBoot( List<CarUxRestrictionsConfiguration> configs)446 public boolean saveUxRestrictionsConfigurationForNextBoot( 447 List<CarUxRestrictionsConfiguration> configs) { 448 ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 449 450 validateConfigs(configs); 451 452 return persistConfig(configs, CONFIG_FILENAME_STAGED); 453 } 454 455 @Override 456 @Nullable getStagedConfigs()457 public List<CarUxRestrictionsConfiguration> getStagedConfigs() { 458 ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 459 460 File stagedConfig = getFile(CONFIG_FILENAME_STAGED); 461 if (stagedConfig.exists()) { 462 logd("Attempting to read staged config"); 463 return readPersistedConfig(stagedConfig); 464 } else { 465 return null; 466 } 467 } 468 469 /** 470 * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to 471 * be applied in the same driving state. Restrictions for each mode can be configured through 472 * {@link CarUxRestrictionsConfiguration}. 473 * 474 * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}. 475 * 476 * @param mode the restriction mode 477 * @return {@code true} if mode was successfully changed; {@code false} otherwise. 478 * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions 479 * @see CarUxRestrictionsConfiguration.Builder 480 */ 481 @Override setRestrictionMode(@onNull String mode)482 public boolean setRestrictionMode(@NonNull String mode) { 483 ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 484 Objects.requireNonNull(mode, "mode must not be null"); 485 486 synchronized (mLock) { 487 if (mRestrictionMode.equals(mode)) { 488 return true; 489 } 490 491 addTransitionLogLocked(TAG, mRestrictionMode, mode, System.currentTimeMillis(), 492 "Restriction mode"); 493 mRestrictionMode = mode; 494 logd("Set restriction mode to: " + mode); 495 496 handleDispatchUxRestrictionsLocked( 497 mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed()); 498 } 499 return true; 500 } 501 502 @Override 503 @NonNull getRestrictionMode()504 public String getRestrictionMode() { 505 ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION); 506 507 synchronized (mLock) { 508 return mRestrictionMode; 509 } 510 } 511 512 /** 513 * Writes configuration into the specified file. 514 * 515 * IO access on file is not thread safe. Caller should ensure threading protection. 516 */ persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename)517 private boolean persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename) { 518 File file = getFile(filename); 519 AtomicFile stagedFile = new AtomicFile(file); 520 FileOutputStream fos; 521 try { 522 fos = stagedFile.startWrite(); 523 } catch (IOException e) { 524 Slog.e(TAG, "Could not open file to persist config", e); 525 return false; 526 } 527 try (JsonWriter jsonWriter = new JsonWriter( 528 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) { 529 writeJson(jsonWriter, configs); 530 } catch (IOException e) { 531 Slog.e(TAG, "Could not persist config", e); 532 stagedFile.failWrite(fos); 533 return false; 534 } 535 stagedFile.finishWrite(fos); 536 return true; 537 } 538 539 @VisibleForTesting writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs)540 void writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs) 541 throws IOException { 542 jsonWriter.beginObject(); 543 jsonWriter.name(JSON_NAME_SCHEMA_VERSION).value(JSON_SCHEMA_VERSION_V2); 544 jsonWriter.name(JSON_NAME_RESTRICTIONS); 545 jsonWriter.beginArray(); 546 for (CarUxRestrictionsConfiguration config : configs) { 547 config.writeJson(jsonWriter); 548 } 549 jsonWriter.endArray(); 550 jsonWriter.endObject(); 551 } 552 553 @Nullable readPersistedConfig(File file)554 private List<CarUxRestrictionsConfiguration> readPersistedConfig(File file) { 555 if (!file.exists()) { 556 Slog.e(TAG, "Could not find config file: " + file.getName()); 557 return null; 558 } 559 560 // Take one pass at the file to check the version and then a second pass to read the 561 // contents. We could assess the version and read in one pass, but we're preferring 562 // clarity over complexity here. 563 int schemaVersion = readFileSchemaVersion(file); 564 565 AtomicFile configFile = new AtomicFile(file); 566 try (JsonReader reader = new JsonReader( 567 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) { 568 List<CarUxRestrictionsConfiguration> configs = new ArrayList<>(); 569 switch (schemaVersion) { 570 case JSON_SCHEMA_VERSION_V1: 571 readV1Json(reader, configs); 572 break; 573 case JSON_SCHEMA_VERSION_V2: 574 readV2Json(reader, configs); 575 break; 576 default: 577 Slog.e(TAG, "Unable to parse schema for version " + schemaVersion); 578 } 579 580 return configs; 581 } catch (IOException e) { 582 Slog.e(TAG, "Could not read persisted config file " + file.getName(), e); 583 } 584 return null; 585 } 586 readV1Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)587 private void readV1Json(JsonReader reader, 588 List<CarUxRestrictionsConfiguration> configs) throws IOException { 589 readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V1); 590 } 591 readV2Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)592 private void readV2Json(JsonReader reader, 593 List<CarUxRestrictionsConfiguration> configs) throws IOException { 594 reader.beginObject(); 595 while (reader.hasNext()) { 596 String name = reader.nextName(); 597 switch (name) { 598 case JSON_NAME_RESTRICTIONS: 599 readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V2); 600 break; 601 default: 602 reader.skipValue(); 603 } 604 } 605 reader.endObject(); 606 } 607 readFileSchemaVersion(File file)608 private int readFileSchemaVersion(File file) { 609 AtomicFile configFile = new AtomicFile(file); 610 try (JsonReader reader = new JsonReader( 611 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) { 612 List<CarUxRestrictionsConfiguration> configs = new ArrayList<>(); 613 if (reader.peek() == JsonToken.BEGIN_ARRAY) { 614 // only schema V1 beings with an array - no need to keep reading 615 reader.close(); 616 return JSON_SCHEMA_VERSION_V1; 617 } else { 618 reader.beginObject(); 619 while (reader.hasNext()) { 620 String name = reader.nextName(); 621 switch (name) { 622 case JSON_NAME_SCHEMA_VERSION: 623 int schemaVersion = reader.nextInt(); 624 // got the version, no need to continue reading 625 reader.close(); 626 return schemaVersion; 627 default: 628 reader.skipValue(); 629 } 630 } 631 reader.endObject(); 632 } 633 } catch (IOException e) { 634 Slog.e(TAG, "Could not read persisted config file " + file.getName(), e); 635 } 636 return UNKNOWN_JSON_SCHEMA_VERSION; 637 } 638 readRestrictionsArray(JsonReader reader, List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)639 private void readRestrictionsArray(JsonReader reader, 640 List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion) 641 throws IOException { 642 reader.beginArray(); 643 while (reader.hasNext()) { 644 configs.add(CarUxRestrictionsConfiguration.readJson(reader, schemaVersion)); 645 } 646 reader.endArray(); 647 } 648 649 /** 650 * Enable/disable UX restrictions change broadcast blocking. 651 * Setting this to true will stop broadcasts of UX restriction change to listeners. 652 * This method works only on debug builds and the caller of this method needs to have the same 653 * signature of the car service. 654 */ setUxRChangeBroadcastEnabled(boolean enable)655 public void setUxRChangeBroadcastEnabled(boolean enable) { 656 if (!isDebugBuild()) { 657 Slog.e(TAG, "Cannot set UX restriction change broadcast."); 658 return; 659 } 660 // Check if the caller has the same signature as that of the car service. 661 if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid()) 662 != PackageManager.SIGNATURE_MATCH) { 663 throw new SecurityException( 664 "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid()) 665 + " does not have the right signature"); 666 } 667 668 synchronized (mLock) { 669 if (enable) { 670 // if enabling it back, send the current restrictions 671 mUxRChangeBroadcastEnabled = enable; 672 handleDispatchUxRestrictionsLocked( 673 mDrivingStateService.getCurrentDrivingState().eventValue, 674 getCurrentSpeed()); 675 } else { 676 // fake parked state, so if the system is currently restricted, the restrictions are 677 // relaxed. 678 handleDispatchUxRestrictionsLocked(DRIVING_STATE_PARKED, 0); 679 mUxRChangeBroadcastEnabled = enable; 680 } 681 } 682 } 683 isDebugBuild()684 private boolean isDebugBuild() { 685 return Build.IS_USERDEBUG || Build.IS_ENG; 686 } 687 688 @Override dump(IndentingPrintWriter writer)689 public void dump(IndentingPrintWriter writer) { 690 synchronized (mLock) { 691 writer.println("*CarUxRestrictionsManagerService*"); 692 mUxRClients.dump(writer, "UX Restrictions Clients "); 693 for (int port : mCurrentUxRestrictions.keySet()) { 694 CarUxRestrictions restrictions = mCurrentUxRestrictions.get(port); 695 writer.printf("Port: 0x%02X UXR: %s\n", port, restrictions.toString()); 696 } 697 if (isDebugBuild()) { 698 writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled); 699 } 700 writer.println("UX Restriction configurations:"); 701 for (CarUxRestrictionsConfiguration config : 702 mCarUxRestrictionsConfigurations.values()) { 703 config.dump(writer); 704 } 705 writer.println("UX Restriction change log:"); 706 for (Utils.TransitionLog tlog : mTransitionLogs) { 707 writer.println(tlog); 708 } 709 writer.println("UX Restriction display info:"); 710 for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) { 711 DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i); 712 writer.printf("Display%d: physicalDisplayId=%d, owner=%s\n", 713 mActivityViewDisplayInfoMap.keyAt(i), info.mPhysicalDisplayId, info.mOwner); 714 } 715 } 716 } 717 718 /** 719 * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService} 720 * for getting driving state change notifications. 721 */ 722 private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener = 723 new ICarDrivingStateChangeListener.Stub() { 724 @Override 725 public void onDrivingStateChanged(CarDrivingStateEvent event) { 726 logd("Driving State Changed:" + event.eventValue); 727 synchronized (mLock) { 728 handleDrivingStateEventLocked(event); 729 } 730 } 731 }; 732 733 /** 734 * Handle the driving state change events coming from the {@link CarDrivingStateService}. 735 * Map the driving state to the corresponding UX Restrictions and dispatch the 736 * UX Restriction change to the registered clients. 737 */ 738 @VisibleForTesting 739 @GuardedBy("mLock") handleDrivingStateEventLocked(CarDrivingStateEvent event)740 void handleDrivingStateEventLocked(CarDrivingStateEvent event) { 741 if (event == null) { 742 return; 743 } 744 int drivingState = event.eventValue; 745 Float speed = getCurrentSpeed(); 746 747 if (speed != SPEED_NOT_AVAILABLE) { 748 mCurrentMovingSpeed = speed; 749 } else if (drivingState == DRIVING_STATE_PARKED 750 || drivingState == DRIVING_STATE_UNKNOWN) { 751 // If speed is unavailable, but the driving state is parked or unknown, it can still be 752 // handled. 753 logd("Speed null when driving state is: " + drivingState); 754 mCurrentMovingSpeed = 0; 755 } else { 756 // If we get here with driving state != parked or unknown && speed == null, 757 // something is wrong. CarDrivingStateService could not have inferred idling or moving 758 // when speed is not available 759 Slog.e(TAG, "Unexpected: Speed null when driving state is: " + drivingState); 760 return; 761 } 762 handleDispatchUxRestrictionsLocked(drivingState, mCurrentMovingSpeed); 763 } 764 765 /** 766 * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting 767 * speed change notifications. 768 */ 769 private final ICarPropertyEventListener mICarPropertyEventListener = 770 new ICarPropertyEventListener.Stub() { 771 @Override 772 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 773 synchronized (mLock) { 774 for (CarPropertyEvent event : events) { 775 if ((event.getEventType() 776 == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) 777 && (event.getCarPropertyValue().getPropertyId() 778 == VehicleProperty.PERF_VEHICLE_SPEED)) { 779 handleSpeedChangeLocked( 780 (Float) event.getCarPropertyValue().getValue()); 781 } 782 } 783 } 784 } 785 }; 786 787 @GuardedBy("mLock") handleSpeedChangeLocked(float newSpeed)788 private void handleSpeedChangeLocked(float newSpeed) { 789 if (newSpeed == mCurrentMovingSpeed) { 790 // Ignore if speed hasn't changed 791 return; 792 } 793 int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue; 794 if (currentDrivingState != DRIVING_STATE_MOVING) { 795 // Ignore speed changes if the vehicle is not moving 796 return; 797 } 798 mCurrentMovingSpeed = newSpeed; 799 handleDispatchUxRestrictionsLocked(currentDrivingState, newSpeed); 800 } 801 802 /** 803 * Handle dispatching UX restrictions change. 804 * 805 * @param currentDrivingState driving state of the vehicle 806 * @param speed speed of the vehicle 807 */ 808 @GuardedBy("mLock") handleDispatchUxRestrictionsLocked(@arDrivingState int currentDrivingState, float speed)809 private void handleDispatchUxRestrictionsLocked(@CarDrivingState int currentDrivingState, 810 float speed) { 811 Objects.requireNonNull(mCarUxRestrictionsConfigurations, 812 "mCarUxRestrictionsConfigurations must be initialized"); 813 Objects.requireNonNull(mCurrentUxRestrictions, 814 "mCurrentUxRestrictions must be initialized"); 815 816 if (isDebugBuild() && !mUxRChangeBroadcastEnabled) { 817 Slog.d(TAG, "Not dispatching UX Restriction due to setting"); 818 return; 819 } 820 821 Map<Integer, CarUxRestrictions> newUxRestrictions = new HashMap<>(); 822 for (int port : mPhysicalPorts) { 823 CarUxRestrictionsConfiguration config = mCarUxRestrictionsConfigurations.get(port); 824 if (config == null) { 825 continue; 826 } 827 828 CarUxRestrictions uxRestrictions = config.getUxRestrictions( 829 currentDrivingState, speed, mRestrictionMode); 830 logd(String.format("Display port 0x%02x\tDO old->new: %b -> %b", 831 port, 832 mCurrentUxRestrictions.get(port).isRequiresDistractionOptimization(), 833 uxRestrictions.isRequiresDistractionOptimization())); 834 logd(String.format("Display port 0x%02x\tUxR old->new: 0x%x -> 0x%x", 835 port, 836 mCurrentUxRestrictions.get(port).getActiveRestrictions(), 837 uxRestrictions.getActiveRestrictions())); 838 newUxRestrictions.put(port, uxRestrictions); 839 } 840 841 // Ignore dispatching if the restrictions has not changed. 842 Set<Integer> displayToDispatch = new ArraySet<>(); 843 for (int port : newUxRestrictions.keySet()) { 844 if (!mCurrentUxRestrictions.containsKey(port)) { 845 // This should never happen. 846 Slog.wtf(TAG, "Unrecognized port:" + port); 847 continue; 848 } 849 CarUxRestrictions uxRestrictions = newUxRestrictions.get(port); 850 if (!mCurrentUxRestrictions.get(port).isSameRestrictions(uxRestrictions)) { 851 displayToDispatch.add(port); 852 } 853 } 854 if (displayToDispatch.isEmpty()) { 855 return; 856 } 857 858 for (int port : displayToDispatch) { 859 addTransitionLogLocked( 860 mCurrentUxRestrictions.get(port), newUxRestrictions.get(port)); 861 } 862 863 dispatchRestrictionsToClients(newUxRestrictions, displayToDispatch); 864 865 mCurrentUxRestrictions = newUxRestrictions; 866 } 867 dispatchRestrictionsToClients(Map<Integer, CarUxRestrictions> displayRestrictions, Set<Integer> displayToDispatch)868 private void dispatchRestrictionsToClients(Map<Integer, CarUxRestrictions> displayRestrictions, 869 Set<Integer> displayToDispatch) { 870 logd("dispatching to clients"); 871 boolean success = mClientDispatchHandler.post(() -> { 872 int numClients = mUxRClients.beginBroadcast(); 873 for (int i = 0; i < numClients; i++) { 874 ICarUxRestrictionsChangeListener callback = mUxRClients.getBroadcastItem(i); 875 RemoteCallbackListCookie cookie = 876 (RemoteCallbackListCookie) mUxRClients.getBroadcastCookie(i); 877 if (!displayToDispatch.contains(cookie.mPhysicalPort)) { 878 continue; 879 } 880 CarUxRestrictions restrictions = displayRestrictions.get(cookie.mPhysicalPort); 881 if (restrictions == null) { 882 // don't dispatch to displays without configurations 883 continue; 884 } 885 try { 886 callback.onUxRestrictionsChanged(restrictions); 887 } catch (RemoteException e) { 888 Slog.e(TAG, 889 String.format("Dispatch to listener %s failed for restrictions (%s)", 890 callback, restrictions)); 891 } 892 } 893 mUxRClients.finishBroadcast(); 894 }); 895 896 if (!success) { 897 Slog.e(TAG, "Unable to post (" + displayRestrictions + ") event to dispatch handler"); 898 } 899 } 900 901 @VisibleForTesting getDefaultDisplayPhysicalPort(DisplayManager displayManager)902 static int getDefaultDisplayPhysicalPort(DisplayManager displayManager) { 903 Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 904 DisplayAddress.Physical address = (DisplayAddress.Physical) defaultDisplay.getAddress(); 905 906 if (address == null) { 907 Slog.e(TAG, "Default display does not have physical display port. Using 0 as port."); 908 return DEFAULT_PORT; 909 } 910 return address.getPort(); 911 } 912 initPhysicalPort()913 private void initPhysicalPort() { 914 for (Display display : mDisplayManager.getDisplays()) { 915 if (display.getType() == Display.TYPE_VIRTUAL) { 916 continue; 917 } 918 919 if (display.getDisplayId() == Display.DEFAULT_DISPLAY && display.getAddress() == null) { 920 // Assume default display is a physical display so assign an address if it 921 // does not have one (possibly due to lower graphic driver version). 922 if (Log.isLoggable(TAG, Log.INFO)) { 923 Slog.i(TAG, "Default display does not have display address. Using default."); 924 } 925 synchronized (mLock) { 926 mPhysicalPorts.add(mDefaultDisplayPhysicalPort); 927 } 928 } else if (display.getAddress() instanceof DisplayAddress.Physical) { 929 int port = ((DisplayAddress.Physical) display.getAddress()).getPort(); 930 if (Log.isLoggable(TAG, Log.INFO)) { 931 Slog.i(TAG, "Display " + display.getDisplayId() + " uses port " + port); 932 } 933 synchronized (mLock) { 934 mPhysicalPorts.add(port); 935 } 936 } else { 937 Slog.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<Integer, CarUxRestrictionsConfiguration> convertToMap( 944 List<CarUxRestrictionsConfiguration> configs) { 945 validateConfigs(configs); 946 947 Map<Integer, CarUxRestrictionsConfiguration> result = new HashMap<>(); 948 if (configs.size() == 1) { 949 CarUxRestrictionsConfiguration config = configs.get(0); 950 synchronized (mLock) { 951 int 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<Integer> 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 Integer 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 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 Integer getPhysicalPortLocked(int displayId) { 1012 if (!mPortLookup.containsKey(displayId)) { 1013 Display display = mDisplayManager.getDisplay(displayId); 1014 if (display == null) { 1015 Slog.w(TAG, "Could not retrieve display for id: " + displayId); 1016 return null; 1017 } 1018 int 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 int doGetPhysicalPortLocked(@NonNull Display display) { 1026 if (display.getType() == Display.TYPE_VIRTUAL) { 1027 Slog.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 Slog.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 Slog.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(int port)1058 CarUxRestrictionsConfiguration createDefaultConfig(int 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 Integer physicalPort = getPhysicalPortLocked(physicalDisplayId); 1156 if (physicalPort == null) { 1157 // This should not happen. 1158 Slog.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