1 /* 2 * Copyright (C) 2019 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.builtin.view.DisplayHelper.INVALID_PORT; 20 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING; 21 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; 22 import static android.view.Display.INVALID_DISPLAY; 23 import static android.view.Display.STATE_ON; 24 25 import static com.android.car.CarServiceUtils.getHandlerThread; 26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 27 import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY; 28 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.annotation.UserIdInt; 32 import android.app.ActivityManager; 33 import android.car.Car; 34 import android.car.CarInfoManager; 35 import android.car.CarOccupantZoneManager; 36 import android.car.CarOccupantZoneManager.DisplayTypeEnum; 37 import android.car.CarOccupantZoneManager.OccupantTypeEnum; 38 import android.car.CarOccupantZoneManager.OccupantZoneInfo; 39 import android.car.ICarOccupantZone; 40 import android.car.ICarOccupantZoneCallback; 41 import android.car.VehicleAreaSeat; 42 import android.car.builtin.util.Slogf; 43 import android.car.builtin.view.DisplayHelper; 44 import android.car.input.CarInputManager; 45 import android.car.media.CarAudioManager; 46 import android.car.user.CarUserManager.UserLifecycleListener; 47 import android.car.user.UserLifecycleEventFilter; 48 import android.content.Context; 49 import android.content.pm.PackageManager; 50 import android.content.res.Resources; 51 import android.hardware.display.DisplayManager; 52 import android.os.Binder; 53 import android.os.Handler; 54 import android.os.Looper; 55 import android.os.RemoteCallbackList; 56 import android.os.RemoteException; 57 import android.os.UserHandle; 58 import android.os.UserManager; 59 import android.util.ArrayMap; 60 import android.util.ArraySet; 61 import android.util.Log; 62 import android.util.SparseArray; 63 import android.util.SparseIntArray; 64 import android.util.proto.ProtoOutputStream; 65 import android.view.Display; 66 67 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 68 import com.android.car.internal.util.IndentingPrintWriter; 69 import com.android.car.internal.util.IntArray; 70 import com.android.car.occupantzone.CarOccupantZoneDumpProto; 71 import com.android.car.occupantzone.CarOccupantZoneDumpProto.DisplayConfigProto; 72 import com.android.car.occupantzone.CarOccupantZoneDumpProto.DisplayPortConfigsProto; 73 import com.android.car.occupantzone.CarOccupantZoneDumpProto.DisplayPortConfigsProto.DisplayConfigPortProto; 74 import com.android.car.occupantzone.CarOccupantZoneDumpProto.DisplayUniqueIdConfigsProto; 75 import com.android.car.occupantzone.CarOccupantZoneDumpProto.DisplayUniqueIdConfigsProto.DisplayConfigUniqueIdProto; 76 import com.android.car.user.CarUserService; 77 import com.android.car.user.UserHandleHelper; 78 import com.android.internal.annotations.GuardedBy; 79 import com.android.internal.annotations.VisibleForTesting; 80 import com.android.internal.util.Preconditions; 81 82 import java.util.ArrayList; 83 import java.util.Arrays; 84 import java.util.List; 85 import java.util.Objects; 86 87 /** 88 * Service to implement CarOccupantZoneManager API. 89 */ 90 public final class CarOccupantZoneService extends ICarOccupantZone.Stub 91 implements CarServiceBase { 92 93 private static final String TAG = CarLog.tagFor(CarOccupantZoneService.class); 94 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 95 96 private static final String HANDLER_THREAD_NAME = "CarOccupantZoneService"; 97 98 private static final int[] EMPTY_INPUT_SUPPORT_TYPES = EMPTY_INT_ARRAY; 99 100 private final Object mLock = new Object(); 101 private final Context mContext; 102 private final DisplayManager mDisplayManager; 103 private final UserManager mUserManager; 104 private CarUserService mCarUserService; 105 106 private final boolean mEnableProfileUserAssignmentForMultiDisplay; 107 108 /** 109 * Stores android user id of profile users for the current user. 110 */ 111 @GuardedBy("mLock") 112 private final ArraySet<Integer> mProfileUsers = new ArraySet<>(); 113 114 /** key: zone id */ 115 @GuardedBy("mLock") 116 private final SparseArray<OccupantZoneInfo> mOccupantsConfig = new SparseArray<>(); 117 118 /** 119 * The config of a display identified by occupant zone id and display type. 120 */ 121 public static final class DisplayConfig { 122 public final int displayType; 123 public final int occupantZoneId; 124 public final int[] inputTypes; 125 DisplayConfig(int displayType, int occupantZoneId, IntArray inputTypes)126 DisplayConfig(int displayType, int occupantZoneId, IntArray inputTypes) { 127 this.displayType = displayType; 128 this.occupantZoneId = occupantZoneId; 129 if (inputTypes == null) { 130 Slogf.w(TAG, "No input type was defined for displayType:%d " 131 + " and occupantZoneId:%d", displayType, occupantZoneId); 132 } 133 this.inputTypes = inputTypes == null ? EMPTY_INPUT_SUPPORT_TYPES : inputTypes.toArray(); 134 } 135 136 @Override toString()137 public String toString() { 138 // do not include type as this is only used for dump 139 StringBuilder b = new StringBuilder(64); 140 b.append("{displayType="); 141 b.append(Integer.toHexString(displayType)); 142 b.append(" occupantZoneId="); 143 b.append(occupantZoneId); 144 b.append(" inputTypes="); 145 b.append(Arrays.toString(inputTypes)); 146 b.append("}"); 147 return b.toString(); 148 } 149 } 150 151 /** key: display port address */ 152 @GuardedBy("mLock") 153 private final SparseArray<DisplayConfig> mDisplayPortConfigs = new SparseArray<>(); 154 155 /** key: displayUniqueId */ 156 @GuardedBy("mLock") 157 private final ArrayMap<String, DisplayConfig> mDisplayUniqueIdConfigs = new ArrayMap<>(); 158 159 /** key: audio zone id */ 160 @GuardedBy("mLock") 161 private final SparseIntArray mAudioZoneIdToOccupantZoneIdMapping = new SparseIntArray(); 162 163 @VisibleForTesting 164 static class DisplayInfo { 165 public final Display display; 166 public final int displayType; 167 DisplayInfo(Display display, int displayType)168 DisplayInfo(Display display, int displayType) { 169 this.display = display; 170 this.displayType = displayType; 171 } 172 173 @Override toString()174 public String toString() { 175 // do not include type as this is only used for dump 176 StringBuilder b = new StringBuilder(64); 177 b.append("{displayId="); 178 b.append(display.getDisplayId()); 179 b.append(" displayType="); 180 b.append(displayType); 181 b.append("}"); 182 return b.toString(); 183 } 184 } 185 186 @VisibleForTesting 187 static class OccupantConfig { 188 public int userId = CarOccupantZoneManager.INVALID_USER_ID; 189 public final ArrayList<DisplayInfo> displayInfos = new ArrayList<>(); 190 public int audioZoneId = CarAudioManager.INVALID_AUDIO_ZONE; 191 192 @Override toString()193 public String toString() { 194 // do not include type as this is only used for dump 195 StringBuilder b = new StringBuilder(128); 196 b.append("{userId="); 197 b.append(userId); 198 b.append(" displays="); 199 for (int i = 0; i < displayInfos.size(); i++) { 200 b.append(displayInfos.get(i).toString()); 201 } 202 b.append(" audioZoneId="); 203 if (audioZoneId != CarAudioManager.INVALID_AUDIO_ZONE) { 204 b.append(audioZoneId); 205 } else { 206 b.append("none"); 207 } 208 b.append("}"); 209 return b.toString(); 210 } 211 } 212 213 /** key : zoneId */ 214 @GuardedBy("mLock") 215 private final SparseArray<OccupantConfig> mActiveOccupantConfigs = new SparseArray<>(); 216 217 @GuardedBy("mLock") 218 private int mDriverZoneId = OccupantZoneInfo.INVALID_ZONE_ID; 219 220 @VisibleForTesting 221 final UserLifecycleListener mUserLifecycleListener = event -> { 222 if (DBG) Slogf.d(TAG, "onEvent(%s)", event); 223 boolean isUserSwitching = (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING); 224 handleUserChange(isUserSwitching); 225 }; 226 227 @VisibleForTesting 228 final DisplayManager.DisplayListener mDisplayListener = 229 new DisplayManager.DisplayListener() { 230 @Override 231 public void onDisplayAdded(int displayId) { 232 handleDisplayChange(displayId); 233 } 234 235 @Override 236 public void onDisplayRemoved(int displayId) { 237 handleDisplayChange(displayId); 238 } 239 240 @Override 241 public void onDisplayChanged(int displayId) { 242 // nothing to do 243 } 244 }; 245 246 private final RemoteCallbackList<ICarOccupantZoneCallback> mClientCallbacks = 247 new RemoteCallbackList<>(); 248 249 @GuardedBy("mLock") 250 private int mDriverSeat = VehicleAreaSeat.SEAT_UNKNOWN; 251 private final UserHandleHelper mUserHandleHelper; 252 253 final Handler mHandler = new Handler(getHandlerThread(HANDLER_THREAD_NAME).getLooper()); 254 CarOccupantZoneService(Context context)255 public CarOccupantZoneService(Context context) { 256 this(context, context.getSystemService(DisplayManager.class), 257 context.getSystemService(UserManager.class), 258 context.getResources().getBoolean( 259 R.bool.enableProfileUserAssignmentForMultiDisplay) 260 && context.getPackageManager().hasSystemFeature( 261 PackageManager.FEATURE_MANAGED_USERS), 262 new UserHandleHelper(context, context.getSystemService(UserManager.class))); 263 } 264 265 @VisibleForTesting CarOccupantZoneService(Context context, DisplayManager displayManager, UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay, UserHandleHelper userHandleHelper)266 public CarOccupantZoneService(Context context, DisplayManager displayManager, 267 UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay, 268 UserHandleHelper userHandleHelper) { 269 mContext = context; 270 mDisplayManager = displayManager; 271 mUserManager = userManager; 272 mEnableProfileUserAssignmentForMultiDisplay = enableProfileUserAssignmentForMultiDisplay; 273 mUserHandleHelper = userHandleHelper; 274 } 275 276 @Override init()277 public void init() { 278 // This does not require connection as binder will be passed directly. 279 Car car = new Car(mContext, /* service= */null, /* handler= */ null); 280 CarInfoManager infoManager = new CarInfoManager(car, CarLocalServices.getService( 281 CarPropertyService.class)); 282 int driverSeat = infoManager.getDriverSeat(); 283 synchronized (mLock) { 284 mDriverSeat = driverSeat; 285 parseOccupantZoneConfigsLocked(); 286 parseDisplayConfigsLocked(); 287 handleActiveDisplaysLocked(); 288 handleAudioZoneChangesLocked(); 289 handleUserChangesLocked(); 290 } 291 mCarUserService = CarLocalServices.getService(CarUserService.class); 292 UserLifecycleEventFilter userEventFilter = new UserLifecycleEventFilter.Builder() 293 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).addEventType( 294 USER_LIFECYCLE_EVENT_TYPE_STOPPING).build(); 295 mCarUserService.addUserLifecycleListener(userEventFilter, mUserLifecycleListener); 296 mDisplayManager.registerDisplayListener(mDisplayListener, 297 new Handler(Looper.getMainLooper())); 298 299 CarServiceHelperWrapper.getInstance().runOnConnection(() -> doSyncWithCarServiceHelper( 300 /* updateDisplay= */ true, /* updateUser= */ true)); 301 } 302 303 @Override release()304 public void release() { 305 mDisplayManager.unregisterDisplayListener(mDisplayListener); 306 mCarUserService.removeUserLifecycleListener(mUserLifecycleListener); 307 synchronized (mLock) { 308 mOccupantsConfig.clear(); 309 mDisplayPortConfigs.clear(); 310 mDisplayUniqueIdConfigs.clear(); 311 mAudioZoneIdToOccupantZoneIdMapping.clear(); 312 mActiveOccupantConfigs.clear(); 313 } 314 } 315 316 /** 317 * Returns the cloned occupant zone configs as read from the config_occupant_zones RRO config. 318 * 319 * <p><b>Note:</b> If the config_occupant_zones string value is empty, an occupant zone info 320 * will be automatically created for the driver and added to the occupant zone configs as the 321 * sole occupant zone info. 322 * 323 * @return cloned occupant zone configs mapped from occupant zone id to occupant zone info 324 */ 325 @NonNull getOccupantsConfig()326 public SparseArray<OccupantZoneInfo> getOccupantsConfig() { 327 synchronized (mLock) { 328 return mOccupantsConfig.clone(); 329 } 330 } 331 332 /** Return cloned mDisplayPortConfigs for testing */ 333 @VisibleForTesting 334 @NonNull getDisplayPortConfigs()335 public SparseArray<DisplayConfig> getDisplayPortConfigs() { 336 synchronized (mLock) { 337 return mDisplayPortConfigs.clone(); 338 } 339 } 340 341 /** Return cloned mDisplayUniqueIdConfigs for testing */ 342 @VisibleForTesting 343 @NonNull getDisplayUniqueIdConfigs()344 ArrayMap<String, DisplayConfig> getDisplayUniqueIdConfigs() { 345 synchronized (mLock) { 346 return new ArrayMap<>(mDisplayUniqueIdConfigs); 347 } 348 } 349 350 /** Return cloned mAudioConfigs for testing */ 351 @VisibleForTesting 352 @NonNull getAudioConfigs()353 SparseIntArray getAudioConfigs() { 354 synchronized (mLock) { 355 return mAudioZoneIdToOccupantZoneIdMapping.clone(); 356 } 357 } 358 359 /** Return cloned mActiveOccupantConfigs for testing */ 360 @VisibleForTesting 361 @NonNull getActiveOccupantConfigs()362 public SparseArray<OccupantConfig> getActiveOccupantConfigs() { 363 synchronized (mLock) { 364 return mActiveOccupantConfigs.clone(); 365 } 366 } 367 368 @Override 369 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)370 public void dump(IndentingPrintWriter writer) { 371 writer.println("*OccupantZoneService*"); 372 synchronized (mLock) { 373 writer.println("**mOccupantsConfig**"); 374 for (int i = 0; i < mOccupantsConfig.size(); ++i) { 375 writer.println(" zoneId=" + mOccupantsConfig.keyAt(i) 376 + " info=" + mOccupantsConfig.valueAt(i)); 377 } 378 writer.println("**mDisplayConfigs**"); 379 for (int i = 0; i < mDisplayPortConfigs.size(); ++i) { 380 writer.println(" port=" + mDisplayPortConfigs.keyAt(i) 381 + " config=" + mDisplayPortConfigs.valueAt(i)); 382 } 383 for (int i = 0; i < mDisplayUniqueIdConfigs.size(); ++i) { 384 writer.println(" uniqueId=" + mDisplayUniqueIdConfigs.keyAt(i) 385 + " config=" + mDisplayUniqueIdConfigs.valueAt(i)); 386 } 387 writer.println("**mAudioZoneIdToOccupantZoneIdMapping**"); 388 for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) { 389 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index); 390 writer.println(" audioZoneId=" + Integer.toHexString(audioZoneId) 391 + " zoneId=" + mAudioZoneIdToOccupantZoneIdMapping.valueAt(index)); 392 } 393 writer.println("**mActiveOccupantConfigs**"); 394 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 395 writer.println(" zoneId=" + mActiveOccupantConfigs.keyAt(i) 396 + " config=" + mActiveOccupantConfigs.valueAt(i)); 397 } 398 writer.println("mEnableProfileUserAssignmentForMultiDisplay:" 399 + mEnableProfileUserAssignmentForMultiDisplay); 400 writer.println("hasDriverZone: " + hasDriverZone()); 401 } 402 } 403 404 @Override 405 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)406 public void dumpProto(ProtoOutputStream proto) { 407 synchronized (mLock) { 408 for (int i = 0; i < mDisplayPortConfigs.size(); i++) { 409 long displayPortConfigsToken = proto.start( 410 CarOccupantZoneDumpProto.DISPLAY_PORT_CONFIGS); 411 long displayConfigPortToken = proto.start( 412 DisplayPortConfigsProto.DISPLAY_CONFIG_PORT); 413 int port = mDisplayPortConfigs.keyAt(i); 414 proto.write(DisplayConfigPortProto.PORT, port); 415 long displayConfigToken = proto.start(DisplayConfigPortProto.DISPLAY_CONFIG); 416 DisplayConfig displayConfig = mDisplayPortConfigs.valueAt(i); 417 proto.write(DisplayConfigProto.DISPLAY_TYPE, displayConfig.displayType); 418 proto.write(DisplayConfigProto.OCCUPANT_ZONE_ID, displayConfig.occupantZoneId); 419 for (int j = 0; j < displayConfig.inputTypes.length; j++) { 420 proto.write(DisplayConfigProto.INPUT_TYPES, displayConfig.inputTypes[j]); 421 } 422 proto.end(displayConfigToken); 423 proto.end(displayConfigPortToken); 424 proto.end(displayPortConfigsToken); 425 } 426 427 for (int i = 0; i < mDisplayUniqueIdConfigs.size(); i++) { 428 long displayUniqueIdConfigsToken = proto.start( 429 CarOccupantZoneDumpProto.DISPLAY_UNIQUE_ID_CONFIGS); 430 long displayConfigUniqueIdToken = proto.start( 431 DisplayUniqueIdConfigsProto.DISPLAY_CONFIG_UNIQUE_ID); 432 String uniqueId = mDisplayUniqueIdConfigs.keyAt(i); 433 proto.write(DisplayConfigUniqueIdProto.UNIQUE_ID, uniqueId); 434 long displayConfigToken = proto.start(DisplayConfigPortProto.DISPLAY_CONFIG); 435 DisplayConfig displayConfig = mDisplayUniqueIdConfigs.valueAt(i); 436 proto.write(DisplayConfigProto.DISPLAY_TYPE, displayConfig.displayType); 437 proto.write(DisplayConfigProto.OCCUPANT_ZONE_ID, displayConfig.occupantZoneId); 438 for (int j = 0; j < displayConfig.inputTypes.length; j++) { 439 proto.write(DisplayConfigProto.INPUT_TYPES, displayConfig.inputTypes[j]); 440 } 441 proto.end(displayConfigToken); 442 proto.end(displayConfigUniqueIdToken); 443 proto.end(displayUniqueIdConfigsToken); 444 } 445 } 446 } 447 448 @Override getAllOccupantZones()449 public List<OccupantZoneInfo> getAllOccupantZones() { 450 synchronized (mLock) { 451 List<OccupantZoneInfo> infos = new ArrayList<>(); 452 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 453 int zoneId = mActiveOccupantConfigs.keyAt(i); 454 // no need for deep copy as OccupantZoneInfo itself is static. 455 infos.add(mOccupantsConfig.get(zoneId)); 456 } 457 return infos; 458 } 459 } 460 461 @Override getAllDisplaysForOccupantZone(int occupantZoneId)462 public int[] getAllDisplaysForOccupantZone(int occupantZoneId) { 463 synchronized (mLock) { 464 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 465 if (config == null) { 466 return EMPTY_INT_ARRAY; 467 } 468 int[] displayIds = new int[config.displayInfos.size()]; 469 for (int i = 0; i < config.displayInfos.size(); i++) { 470 displayIds[i] = config.displayInfos.get(i).display.getDisplayId(); 471 } 472 return displayIds; 473 } 474 } 475 476 /** 477 * Checks if all displays for a given OccupantZone are on. 478 * 479 * @param occupantZoneId indicates which OccupantZone's displays to check 480 * @return whether all displays for a given OccupantZone are on 481 */ areDisplaysOnForOccupantZone(int occupantZoneId)482 public boolean areDisplaysOnForOccupantZone(int occupantZoneId) { 483 synchronized (mLock) { 484 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 485 if (config == null) { 486 return false; 487 } 488 for (int i = 0; i < config.displayInfos.size(); i++) { 489 if (config.displayInfos.get(i).display.getState() != STATE_ON) { 490 return false; 491 } 492 } 493 494 return true; 495 } 496 } 497 498 @Override getDisplayForOccupant(int occupantZoneId, int displayType)499 public int getDisplayForOccupant(int occupantZoneId, int displayType) { 500 synchronized (mLock) { 501 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 502 if (config == null) { 503 return Display.INVALID_DISPLAY; 504 } 505 for (int i = 0; i < config.displayInfos.size(); i++) { 506 if (displayType == config.displayInfos.get(i).displayType) { 507 return config.displayInfos.get(i).display.getDisplayId(); 508 } 509 } 510 } 511 return Display.INVALID_DISPLAY; 512 } 513 getAllDisplayIdsForDriver(int displayType)514 public IntArray getAllDisplayIdsForDriver(int displayType) { 515 synchronized (mLock) { 516 OccupantConfig config = mActiveOccupantConfigs.get(mDriverZoneId); 517 if (config == null) { 518 return new IntArray(0); 519 } 520 IntArray displayIds = new IntArray(config.displayInfos.size()); 521 for (int i = 0; i < config.displayInfos.size(); i++) { 522 DisplayInfo displayInfo = config.displayInfos.get(i); 523 if (displayInfo.displayType == displayType) { 524 displayIds.add(displayInfo.display.getDisplayId()); 525 } 526 } 527 return displayIds; 528 } 529 } 530 531 @Override getDisplayIdForDriver(@isplayTypeEnum int displayType)532 public int getDisplayIdForDriver(@DisplayTypeEnum int displayType) { 533 enforcePermission(Car.ACCESS_PRIVATE_DISPLAY_ID); 534 synchronized (mLock) { 535 int driverUserId = getDriverUserId(); 536 DisplayInfo displayInfo = findDisplayForDriverLocked(driverUserId, displayType); 537 if (displayInfo == null) { 538 return Display.INVALID_DISPLAY; 539 } 540 return displayInfo.display.getDisplayId(); 541 } 542 } 543 544 @GuardedBy("mLock") 545 @Nullable findDisplayForDriverLocked(@serIdInt int driverUserId, @DisplayTypeEnum int displayType)546 private DisplayInfo findDisplayForDriverLocked(@UserIdInt int driverUserId, 547 @DisplayTypeEnum int displayType) { 548 for (OccupantZoneInfo zoneInfo : getAllOccupantZones()) { 549 if (zoneInfo.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) { 550 OccupantConfig config = mActiveOccupantConfigs.get(zoneInfo.zoneId); 551 if (config == null) { 552 //No active display for zone, just continue... 553 continue; 554 } 555 556 if (config.userId == driverUserId) { 557 for (DisplayInfo displayInfo : config.displayInfos) { 558 if (displayInfo.displayType == displayType) { 559 return displayInfo; 560 } 561 } 562 } 563 } 564 } 565 return null; 566 } 567 568 @Override getAudioZoneIdForOccupant(int occupantZoneId)569 public int getAudioZoneIdForOccupant(int occupantZoneId) { 570 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 571 synchronized (mLock) { 572 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 573 if (config != null) { 574 return config.audioZoneId; 575 } 576 // check if the occupant id exist at all 577 if (!mOccupantsConfig.contains(occupantZoneId)) { 578 return CarAudioManager.INVALID_AUDIO_ZONE; 579 } 580 // Exist but not active 581 return getAudioZoneIdForOccupantLocked(occupantZoneId); 582 } 583 } 584 585 @GuardedBy("mLock") getAudioZoneIdForOccupantLocked(int occupantZoneId)586 private int getAudioZoneIdForOccupantLocked(int occupantZoneId) { 587 for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) { 588 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index); 589 if (occupantZoneId == mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId)) { 590 return audioZoneId; 591 } 592 } 593 return CarAudioManager.INVALID_AUDIO_ZONE; 594 } 595 596 @Override getOccupantForAudioZoneId(int audioZoneId)597 public OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) { 598 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 599 synchronized (mLock) { 600 int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId, 601 OccupantZoneInfo.INVALID_ZONE_ID); 602 if (occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 603 return null; 604 } 605 // To support headless zones return the occupant configuration. 606 return mOccupantsConfig.get(occupantZoneId); 607 } 608 } 609 610 /** 611 * Finds the DisplayConfig for a logical display id. 612 */ 613 @Nullable findDisplayConfigForDisplayId(int displayId)614 public DisplayConfig findDisplayConfigForDisplayId(int displayId) { 615 synchronized (mLock) { 616 return findDisplayConfigForDisplayIdLocked(displayId); 617 } 618 } 619 620 /** 621 * Finds the DisplayConfig for a physical display port. 622 */ 623 @Nullable findDisplayConfigForPort(int portAddress)624 public DisplayConfig findDisplayConfigForPort(int portAddress) { 625 synchronized (mLock) { 626 return findDisplayConfigForPortLocked(portAddress); 627 } 628 } 629 630 @GuardedBy("mLock") 631 @Nullable findDisplayConfigForDisplayIdLocked(int displayId)632 private DisplayConfig findDisplayConfigForDisplayIdLocked(int displayId) { 633 Display display = mDisplayManager.getDisplay(displayId); 634 if (display == null) { 635 return null; 636 } 637 return findDisplayConfigForDisplayLocked(display); 638 } 639 640 @GuardedBy("mLock") 641 @Nullable findDisplayConfigForDisplayLocked(Display display)642 private DisplayConfig findDisplayConfigForDisplayLocked(Display display) { 643 int portAddress = DisplayHelper.getPhysicalPort(display); 644 if (portAddress != INVALID_PORT) { 645 DisplayConfig config = mDisplayPortConfigs.get(portAddress); 646 if (config != null) { 647 return config; 648 } 649 } 650 return mDisplayUniqueIdConfigs.get(DisplayHelper.getUniqueId(display)); 651 } 652 653 @GuardedBy("mLock") 654 @Nullable findDisplayConfigForPortLocked(int portAddress)655 private DisplayConfig findDisplayConfigForPortLocked(int portAddress) { 656 return portAddress != INVALID_PORT ? mDisplayPortConfigs.get(portAddress) : null; 657 } 658 659 @Override getDisplayType(int displayId)660 public int getDisplayType(int displayId) { 661 synchronized (mLock) { 662 DisplayConfig config = findDisplayConfigForDisplayIdLocked(displayId); 663 if (config != null) { 664 return config.displayType; 665 } 666 } 667 return CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN; 668 } 669 670 @Override getUserForOccupant(int occupantZoneId)671 public int getUserForOccupant(int occupantZoneId) { 672 synchronized (mLock) { 673 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 674 if (config == null) { 675 return CarOccupantZoneManager.INVALID_USER_ID; 676 } 677 return config.userId; 678 } 679 } 680 681 @Override getOccupantZoneIdForUserId(@serIdInt int userId)682 public int getOccupantZoneIdForUserId(@UserIdInt int userId) { 683 synchronized (mLock) { 684 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 685 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 686 if (config.userId == userId) { 687 return mActiveOccupantConfigs.keyAt(i); 688 } 689 } 690 Slogf.w(TAG, "Could not find occupantZoneId for userId%d returning invalid " 691 + "occupant zone id %d", userId, OccupantZoneInfo.INVALID_ZONE_ID); 692 return OccupantZoneInfo.INVALID_ZONE_ID; 693 } 694 } 695 696 @Override getOccupantZoneForDisplayId(int displayId)697 public OccupantZoneInfo getOccupantZoneForDisplayId(int displayId) { 698 synchronized (mLock) { 699 DisplayConfig displayConfig = findDisplayConfigForDisplayIdLocked(displayId); 700 if (displayConfig == null) { 701 Slogf.w(TAG, "getOccupantZoneForDisplayId: Could not find DisplayConfig for " 702 + "display Id %d", displayId); 703 return null; 704 } 705 706 int occupantZoneId = displayConfig.occupantZoneId; 707 if (occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 708 Slogf.w(TAG, "getOccupantZoneForDisplayId: Got invalid occupant zone id from " 709 + "DisplayConfig: %s", displayConfig); 710 return null; 711 } 712 713 return mOccupantsConfig.get(occupantZoneId); 714 } 715 } 716 717 /** 718 * returns the current driver user id. 719 */ getDriverUserId()720 public @UserIdInt int getDriverUserId() { 721 return getCurrentUser(); 722 } 723 724 /** 725 * Sets the mapping for audio zone id to occupant zone id. 726 * 727 * @param audioZoneIdToOccupantZoneMapping map for audio zone id, where key is the audio zone id 728 * and value is the occupant zone id 729 */ setAudioZoneIdsForOccupantZoneIds( @onNull SparseIntArray audioZoneIdToOccupantZoneMapping)730 public void setAudioZoneIdsForOccupantZoneIds( 731 @NonNull SparseIntArray audioZoneIdToOccupantZoneMapping) { 732 Objects.requireNonNull(audioZoneIdToOccupantZoneMapping, 733 "audioZoneIdToOccupantZoneMapping can not be null"); 734 synchronized (mLock) { 735 validateOccupantZoneIdsLocked(audioZoneIdToOccupantZoneMapping); 736 mAudioZoneIdToOccupantZoneIdMapping.clear(); 737 for (int index = 0; index < audioZoneIdToOccupantZoneMapping.size(); index++) { 738 int audioZoneId = audioZoneIdToOccupantZoneMapping.keyAt(index); 739 mAudioZoneIdToOccupantZoneIdMapping.put(audioZoneId, 740 audioZoneIdToOccupantZoneMapping.get(audioZoneId)); 741 } 742 //If there are any active displays for the zone send change event 743 handleAudioZoneChangesLocked(); 744 } 745 syncStateAndSendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO); 746 } 747 748 @GuardedBy("mLock") validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping)749 private void validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping) { 750 for (int i = 0; i < audioZoneIdToOccupantZoneMapping.size(); i++) { 751 int occupantZoneId = 752 audioZoneIdToOccupantZoneMapping.get(audioZoneIdToOccupantZoneMapping.keyAt(i)); 753 if (!mOccupantsConfig.contains(occupantZoneId)) { 754 throw new IllegalArgumentException("occupantZoneId " + occupantZoneId 755 + " does not exist."); 756 } 757 } 758 } 759 760 @Override registerCallback(ICarOccupantZoneCallback callback)761 public void registerCallback(ICarOccupantZoneCallback callback) { 762 mClientCallbacks.register(callback); 763 } 764 765 @Override unregisterCallback(ICarOccupantZoneCallback callback)766 public void unregisterCallback(ICarOccupantZoneCallback callback) { 767 mClientCallbacks.unregister(callback); 768 } 769 770 @Override assignProfileUserToOccupantZone(int occupantZoneId, @UserIdInt int userId)771 public boolean assignProfileUserToOccupantZone(int occupantZoneId, @UserIdInt int userId) { 772 CarServiceUtils.assertAnyPermission(mContext, android.Manifest.permission.MANAGE_USERS, 773 Car.PERMISSION_MANAGE_OCCUPANT_ZONE); 774 775 if (!mEnableProfileUserAssignmentForMultiDisplay) { 776 throw new IllegalStateException("feature not enabled"); 777 } 778 779 UserHandle user = null; 780 synchronized (mLock) { 781 if (occupantZoneId == mDriverZoneId) { 782 throw new IllegalArgumentException("Driver zone cannot have profile user"); 783 } 784 updateEnabledProfilesLocked(getCurrentUser()); 785 786 if (!mProfileUsers.contains(userId) 787 && userId != CarOccupantZoneManager.INVALID_USER_ID) { 788 // current user can change while this call is happening, so return false rather 789 // than throwing exception 790 Slogf.w(TAG, "Invalid profile user id: %d", userId); 791 return false; 792 } 793 if (userId != CarOccupantZoneManager.INVALID_USER_ID) { 794 user = UserHandle.of(userId); 795 } 796 } 797 798 long token = Binder.clearCallingIdentity(); 799 try { 800 if (userId == CarOccupantZoneManager.INVALID_USER_ID) { 801 return unassignOccupantZoneUnchecked(occupantZoneId) 802 == CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK; 803 } else { 804 return assignVisibleUserToOccupantZoneUnchecked(occupantZoneId, user) 805 == CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK; 806 } 807 } finally { 808 Binder.restoreCallingIdentity(token); 809 } 810 } 811 812 @Override assignVisibleUserToOccupantZone(int occupantZoneId, UserHandle user)813 public int assignVisibleUserToOccupantZone(int occupantZoneId, UserHandle user) { 814 CarServiceUtils.assertAnyPermission(mContext, android.Manifest.permission.MANAGE_USERS, 815 Car.PERMISSION_MANAGE_OCCUPANT_ZONE); 816 Preconditions.checkNotNull(user); 817 long token = Binder.clearCallingIdentity(); 818 try { 819 return assignVisibleUserToOccupantZoneUnchecked(occupantZoneId, user); 820 } finally { 821 Binder.restoreCallingIdentity(token); 822 } 823 } 824 825 /** 826 * Precondition: permission check should be done and binder caller identity should be cleared. 827 */ assignVisibleUserToOccupantZoneUnchecked(int occupantZoneId, @NonNull UserHandle user)828 private int assignVisibleUserToOccupantZoneUnchecked(int occupantZoneId, 829 @NonNull UserHandle user) { 830 int userId; 831 if (user.equals(UserHandle.CURRENT)) { 832 userId = getCurrentUser(); 833 } else { 834 userId = user.getIdentifier(); 835 } 836 837 if (!mCarUserService.isUserVisible(userId)) { 838 Slogf.w(TAG, "Non-visible user %d cannot be allocated to zone %d", userId, 839 occupantZoneId); 840 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_FAIL_NON_VISIBLE_USER; 841 } 842 843 synchronized (mLock) { 844 int userZoneId = getZoneIdForUserIdLocked(userId); 845 if (userZoneId != OccupantZoneInfo.INVALID_ZONE_ID 846 && mActiveOccupantConfigs.keyAt(userZoneId) != occupantZoneId) { 847 Slogf.w(TAG, "Cannot assign visible user %d to two different zones simultaneously," 848 + " user is already assigned to %d", 849 userId, userZoneId); 850 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_FAIL_ALREADY_ASSIGNED; 851 } 852 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 853 if (config == null) { 854 Slogf.w(TAG, "Invalid zone:%d", occupantZoneId); 855 throw new IllegalArgumentException("Invalid occupantZoneId:" + occupantZoneId); 856 } 857 if (config.userId == userId && userId != CarOccupantZoneManager.INVALID_USER_ID) { 858 Slogf.w(TAG, "assignVisibleUserToOccupantZone zone:%d already set to user:%d", 859 occupantZoneId, userId); 860 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK; 861 } 862 if (DBG) Slogf.d(TAG, "Assigned user %d to zone %d", userId, occupantZoneId); 863 config.userId = userId; 864 } 865 866 syncStateAndSendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER); 867 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK; 868 } 869 870 @GuardedBy("mLock") getZoneIdForUserIdLocked(@serIdInt int userId)871 private int getZoneIdForUserIdLocked(@UserIdInt int userId) { 872 for (int i = 0; i < mActiveOccupantConfigs.size(); i++) { 873 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 874 if (config.userId == userId) { 875 return mActiveOccupantConfigs.keyAt(i); 876 } 877 } 878 return OccupantZoneInfo.INVALID_ZONE_ID; 879 } 880 881 @Override unassignOccupantZone(int occupantZoneId)882 public int unassignOccupantZone(int occupantZoneId) { 883 CarServiceUtils.assertAnyPermission(mContext, android.Manifest.permission.MANAGE_USERS, 884 Car.PERMISSION_MANAGE_OCCUPANT_ZONE); 885 886 long token = Binder.clearCallingIdentity(); 887 try { 888 return unassignOccupantZoneUnchecked(occupantZoneId); 889 } finally { 890 Binder.restoreCallingIdentity(token); 891 } 892 } 893 894 /** 895 * Precondition: permission check should be done and binder caller identity should be cleared. 896 */ unassignOccupantZoneUnchecked(int occupantZoneId)897 private int unassignOccupantZoneUnchecked(int occupantZoneId) { 898 synchronized (mLock) { 899 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 900 if (config == null) { 901 Slogf.w(TAG, "Invalid zone:%d", occupantZoneId); 902 throw new IllegalArgumentException("Invalid occupantZoneId:" + occupantZoneId); 903 } 904 if (config.userId == CarOccupantZoneManager.INVALID_USER_ID) { 905 // already unassigned 906 Slogf.w(TAG, "unassignOccupantZone for already unassigned zone:%d", 907 occupantZoneId); 908 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK; 909 } 910 OccupantZoneInfo info = mOccupantsConfig.get(occupantZoneId); 911 if (info.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) { 912 Slogf.w(TAG, "Cannot unassign driver zone"); 913 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_FAIL_DRIVER_ZONE; 914 } 915 if (DBG) Slogf.d(TAG, "Unassigned zone:%d", occupantZoneId); 916 config.userId = CarOccupantZoneManager.INVALID_USER_ID; 917 } 918 syncStateAndSendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER); 919 920 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK; 921 } 922 923 @Override getMyOccupantZone()924 public OccupantZoneInfo getMyOccupantZone() { 925 int uid = Binder.getCallingUid(); 926 // UserHandle.getUserId(uid) can do this in one step but it is hidden API. 927 UserHandle user = UserHandle.getUserHandleForUid(uid); 928 int userId = user.getIdentifier(); 929 synchronized (mLock) { 930 for (int i = 0; i < mActiveOccupantConfigs.size(); i++) { 931 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 932 if (config.userId == userId) { 933 int zoneId = mActiveOccupantConfigs.keyAt(i); 934 return mOccupantsConfig.get(zoneId); 935 } 936 } 937 } 938 Slogf.w(TAG, "getMyOccupantZone: No assigned zone for uid:%d", uid); 939 return null; 940 } 941 942 @Override getOccupantZoneForUser(UserHandle user)943 public OccupantZoneInfo getOccupantZoneForUser(UserHandle user) { 944 Objects.requireNonNull(user, "User cannot be null"); 945 if (user.getIdentifier() == CarOccupantZoneManager.INVALID_USER_ID) { 946 return null; 947 } 948 int occupantZoneId = getOccupantZoneIdForUserId(user.getIdentifier()); 949 if (DBG) Slogf.d(TAG, "occupantZoneId that was gotten was %d", occupantZoneId); 950 synchronized (mLock) { 951 return mOccupantsConfig.get(occupantZoneId); 952 } 953 } 954 955 @Override getOccupantZone(@ccupantTypeEnum int occupantType, @VehicleAreaSeat.Enum int seat)956 public OccupantZoneInfo getOccupantZone(@OccupantTypeEnum int occupantType, 957 @VehicleAreaSeat.Enum int seat) { 958 synchronized (mLock) { 959 for (int i = 0; i < mActiveOccupantConfigs.size(); i++) { 960 int zoneId = mActiveOccupantConfigs.keyAt(i); 961 OccupantZoneInfo info = mOccupantsConfig.get(zoneId); 962 if (occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER 963 || occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER) { 964 if (occupantType == info.occupantType) { 965 return info; 966 } 967 } else { 968 if (occupantType == info.occupantType && seat == info.seat) { 969 return info; 970 } 971 } 972 } 973 return null; 974 } 975 } 976 977 /** 978 * Gets the occupant zone id for the seat 979 * 980 * @param seat The vehicle area seat to be used 981 * 982 * @return The occupant zone id for the given seat 983 */ getOccupantZoneIdForSeat(@ehicleAreaSeat.Enum int seat)984 public int getOccupantZoneIdForSeat(@VehicleAreaSeat.Enum int seat) { 985 synchronized (mLock) { 986 return getOccupantZoneIdForSeatLocked(seat); 987 } 988 } 989 990 @GuardedBy("mLock") getOccupantZoneIdForSeatLocked(@ehicleAreaSeat.Enum int seat)991 private int getOccupantZoneIdForSeatLocked(@VehicleAreaSeat.Enum int seat) { 992 for (int i = 0; i < mActiveOccupantConfigs.size(); i++) { 993 int zoneId = mActiveOccupantConfigs.keyAt(i); 994 OccupantZoneInfo info = mOccupantsConfig.get(zoneId); 995 if (seat == info.seat) { 996 return zoneId; 997 } 998 } 999 return OccupantZoneInfo.INVALID_ZONE_ID; 1000 } 1001 1002 @Override hasDriverZone()1003 public boolean hasDriverZone() { 1004 synchronized (mLock) { 1005 return mDriverZoneId != OccupantZoneInfo.INVALID_ZONE_ID; 1006 } 1007 } 1008 1009 @Override hasPassengerZones()1010 public boolean hasPassengerZones() { 1011 synchronized (mLock) { 1012 // There can be only one driver zone. So if there is driver, there should be at least 1013 // two zones to have passenger. If there is no driver zone, having a zone is enough to 1014 // have passenger zone. 1015 boolean hasDriver = mDriverZoneId != OccupantZoneInfo.INVALID_ZONE_ID; 1016 return mActiveOccupantConfigs.size() > (hasDriver ? 1 : 0); 1017 } 1018 } 1019 1020 @Override 1021 @UserIdInt getUserForDisplayId(int displayId)1022 public int getUserForDisplayId(int displayId) { 1023 synchronized (mLock) { 1024 for (int i = 0; i < mActiveOccupantConfigs.size(); i++) { 1025 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 1026 for (int j = 0; j < config.displayInfos.size(); j++) { 1027 if (config.displayInfos.get(j).display.getDisplayId() == displayId) { 1028 return config.userId; 1029 } 1030 } 1031 } 1032 } 1033 Slogf.w(TAG, "Could not find OccupantZone for display Id %d", displayId); 1034 return CarOccupantZoneManager.INVALID_USER_ID; 1035 } 1036 1037 /** Returns number of passenger zones in the device. */ getNumberOfPassengerZones()1038 public int getNumberOfPassengerZones() { 1039 synchronized (mLock) { 1040 boolean hasDriver = mDriverZoneId != OccupantZoneInfo.INVALID_ZONE_ID; 1041 return mActiveOccupantConfigs.size() - (hasDriver ? 1 : 0); 1042 } 1043 } 1044 doSyncWithCarServiceHelper(boolean updateDisplay, boolean updateUser)1045 private void doSyncWithCarServiceHelper(boolean updateDisplay, boolean updateUser) { 1046 int[] passengerDisplays = null; 1047 ArrayMap<Integer, IntArray> allowlists = null; 1048 synchronized (mLock) { 1049 if (updateDisplay) { 1050 passengerDisplays = getAllActivePassengerDisplaysLocked(); 1051 } 1052 if (updateUser) { 1053 allowlists = createDisplayAllowlistsLocked(); 1054 } 1055 } 1056 if (updateDisplay) { 1057 updatePassengerDisplays(passengerDisplays); 1058 } 1059 if (updateUser) { 1060 updateUserAssignmentForDisplays(allowlists); 1061 } 1062 } 1063 1064 @GuardedBy("mLock") getAllActivePassengerDisplaysLocked()1065 private int[] getAllActivePassengerDisplaysLocked() { 1066 IntArray displays = new IntArray(); 1067 for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) { 1068 int zoneId = mActiveOccupantConfigs.keyAt(j); 1069 if (zoneId == mDriverZoneId) { 1070 continue; 1071 } 1072 OccupantConfig config = mActiveOccupantConfigs.valueAt(j); 1073 for (int i = 0; i < config.displayInfos.size(); i++) { 1074 displays.add(config.displayInfos.get(i).display.getDisplayId()); 1075 } 1076 } 1077 return displays.toArray(); 1078 } 1079 updatePassengerDisplays(int[] passengerDisplayIds)1080 private void updatePassengerDisplays(int[] passengerDisplayIds) { 1081 if (passengerDisplayIds == null) { 1082 return; 1083 } 1084 CarServiceHelperWrapper.getInstance().setPassengerDisplays(passengerDisplayIds); 1085 } 1086 1087 @GuardedBy("mLock") createDisplayAllowlistsLocked()1088 private ArrayMap<Integer, IntArray> createDisplayAllowlistsLocked() { 1089 ArrayMap<Integer, IntArray> allowlists = new ArrayMap<>(); 1090 for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) { 1091 int zoneId = mActiveOccupantConfigs.keyAt(j); 1092 if (zoneId == mDriverZoneId) { 1093 continue; 1094 } 1095 OccupantConfig config = mActiveOccupantConfigs.valueAt(j); 1096 if (config.displayInfos.isEmpty()) { 1097 continue; 1098 } 1099 // Do not allow any user if it is unassigned. 1100 if (config.userId == CarOccupantZoneManager.INVALID_USER_ID) { 1101 continue; 1102 } 1103 IntArray displays = allowlists.get(config.userId); 1104 if (displays == null) { 1105 displays = new IntArray(); 1106 allowlists.put(config.userId, displays); 1107 } 1108 for (int i = 0; i < config.displayInfos.size(); i++) { 1109 displays.add(config.displayInfos.get(i).display.getDisplayId()); 1110 } 1111 } 1112 return allowlists; 1113 } 1114 updateUserAssignmentForDisplays(ArrayMap<Integer, IntArray> allowlists)1115 private void updateUserAssignmentForDisplays(ArrayMap<Integer, IntArray> allowlists) { 1116 if (allowlists == null || allowlists.isEmpty()) { 1117 return; 1118 } 1119 for (int i = 0; i < allowlists.size(); i++) { 1120 int userId = allowlists.keyAt(i); 1121 CarServiceHelperWrapper.getInstance().setDisplayAllowlistForUser(userId, 1122 allowlists.valueAt(i).toArray()); 1123 } 1124 } 1125 throwFormatErrorInOccupantZones(String msg)1126 private void throwFormatErrorInOccupantZones(String msg) { 1127 throw new RuntimeException("Format error in config_occupant_zones resource:" + msg); 1128 } 1129 1130 /** Returns the driver seat. */ getDriverSeat()1131 int getDriverSeat() { 1132 synchronized (mLock) { 1133 return mDriverSeat; 1134 } 1135 } 1136 1137 @GuardedBy("mLock") parseOccupantZoneConfigsLocked()1138 private void parseOccupantZoneConfigsLocked() { 1139 final Resources res = mContext.getResources(); 1140 // examples: 1141 // <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item> 1142 // <item>occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1, 1143 // searSide=oppositeDriver</item> 1144 boolean hasDriver = false; 1145 int driverSeat = getDriverSeat(); 1146 int driverSeatSide = VehicleAreaSeat.SIDE_LEFT; // default LHD : Left Hand Drive 1147 if (driverSeat == VehicleAreaSeat.SEAT_ROW_1_RIGHT) { 1148 driverSeatSide = VehicleAreaSeat.SIDE_RIGHT; 1149 } 1150 int maxZoneId = OccupantZoneInfo.INVALID_ZONE_ID; 1151 for (String config : res.getStringArray(R.array.config_occupant_zones)) { 1152 int zoneId = OccupantZoneInfo.INVALID_ZONE_ID; 1153 int type = CarOccupantZoneManager.OCCUPANT_TYPE_INVALID; 1154 int seatRow = 0; // invalid row 1155 int seatSide = VehicleAreaSeat.SIDE_LEFT; 1156 String[] entries = config.split(","); 1157 for (String entry : entries) { 1158 String[] keyValuePair = entry.split("="); 1159 if (keyValuePair.length != 2) { 1160 throwFormatErrorInOccupantZones("No key/value pair:" + entry); 1161 } 1162 switch (keyValuePair[0]) { 1163 case "occupantZoneId": 1164 zoneId = Integer.parseInt(keyValuePair[1]); 1165 break; 1166 case "occupantType": 1167 switch (keyValuePair[1]) { 1168 case "DRIVER": 1169 type = CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER; 1170 break; 1171 case "FRONT_PASSENGER": 1172 type = CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER; 1173 break; 1174 case "REAR_PASSENGER": 1175 type = CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER; 1176 break; 1177 default: 1178 throwFormatErrorInOccupantZones("Unrecognized type:" + entry); 1179 break; 1180 } 1181 break; 1182 case "seatRow": 1183 seatRow = Integer.parseInt(keyValuePair[1]); 1184 break; 1185 case "seatSide": 1186 switch (keyValuePair[1]) { 1187 case "driver": 1188 seatSide = driverSeatSide; 1189 break; 1190 case "oppositeDriver": 1191 seatSide = -driverSeatSide; 1192 break; 1193 case "left": 1194 seatSide = VehicleAreaSeat.SIDE_LEFT; 1195 break; 1196 case "center": 1197 seatSide = VehicleAreaSeat.SIDE_CENTER; 1198 break; 1199 case "right": 1200 seatSide = VehicleAreaSeat.SIDE_RIGHT; 1201 break; 1202 default: 1203 throwFormatErrorInOccupantZones("Unrecognized seatSide:" + entry); 1204 break; 1205 } 1206 break; 1207 default: 1208 throwFormatErrorInOccupantZones("Unrecognized key:" + entry); 1209 break; 1210 } 1211 } 1212 if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 1213 throwFormatErrorInOccupantZones("Missing zone id:" + config); 1214 } 1215 if (zoneId > maxZoneId) { 1216 maxZoneId = zoneId; 1217 } 1218 if (type == CarOccupantZoneManager.OCCUPANT_TYPE_INVALID) { 1219 throwFormatErrorInOccupantZones("Missing type:" + config); 1220 } 1221 if (type == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) { 1222 if (hasDriver) { 1223 throwFormatErrorInOccupantZones("Multiple driver:" + config); 1224 } else { 1225 hasDriver = true; 1226 mDriverZoneId = zoneId; 1227 } 1228 } 1229 int seat = VehicleAreaSeat.fromRowAndSide(seatRow, seatSide); 1230 if (seat == VehicleAreaSeat.SEAT_UNKNOWN) { 1231 throwFormatErrorInOccupantZones("Invalid seat:" + config); 1232 } 1233 OccupantZoneInfo info = new OccupantZoneInfo(zoneId, type, seat); 1234 if (mOccupantsConfig.contains(zoneId)) { 1235 throwFormatErrorInOccupantZones("Duplicate zone id:" + config); 1236 } 1237 mOccupantsConfig.put(zoneId, info); 1238 } 1239 // No zones defined. Then populate driver zone. 1240 if (maxZoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 1241 maxZoneId++; 1242 mDriverZoneId = maxZoneId; 1243 Slogf.w(TAG, "No zones defined, add one as driver:%d", mDriverZoneId); 1244 OccupantZoneInfo info = new OccupantZoneInfo(mDriverZoneId, 1245 CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER, getDriverSeat()); 1246 mOccupantsConfig.put(mDriverZoneId, info); 1247 } 1248 } 1249 throwFormatErrorInDisplayMapping(String msg)1250 private void throwFormatErrorInDisplayMapping(String msg) { 1251 throw new RuntimeException( 1252 "Format error in config_occupant_display_mapping resource:" + msg); 1253 } 1254 1255 @GuardedBy("mLock") parseDisplayConfigsLocked()1256 private void parseDisplayConfigsLocked() { 1257 final Resources res = mContext.getResources(); 1258 final SparseArray<IntArray> inputTypesPerDisplay = new SparseArray<>(); 1259 // examples: 1260 // <item>displayPort=0,displayType=MAIN,occupantZoneId=0,inputTypes=DPAD_KEYS| 1261 // NAVIGATE_KEYS|ROTARY_NAVIGATION</item> 1262 // <item>displayPort=1,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0, 1263 // inputTypes=DPAD_KEYS</item> 1264 for (String config : res.getStringArray(R.array.config_occupant_display_mapping)) { 1265 int port = INVALID_PORT; 1266 String uniqueId = null; 1267 int type = CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN; 1268 int zoneId = OccupantZoneInfo.INVALID_ZONE_ID; 1269 String[] entries = config.split(","); 1270 for (String entry : entries) { 1271 String[] keyValuePair = entry.split("="); 1272 if (keyValuePair.length != 2) { 1273 throwFormatErrorInDisplayMapping("No key/value pair:" + entry); 1274 } 1275 switch (keyValuePair[0]) { 1276 case "displayPort": 1277 port = Integer.parseInt(keyValuePair[1]); 1278 break; 1279 case "displayUniqueId": 1280 uniqueId = keyValuePair[1]; 1281 break; 1282 case "displayType": 1283 switch (keyValuePair[1]) { 1284 case "MAIN": 1285 type = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1286 break; 1287 case "INSTRUMENT_CLUSTER": 1288 type = CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER; 1289 break; 1290 case "HUD": 1291 type = CarOccupantZoneManager.DISPLAY_TYPE_HUD; 1292 break; 1293 case "INPUT": 1294 type = CarOccupantZoneManager.DISPLAY_TYPE_INPUT; 1295 break; 1296 case "AUXILIARY": 1297 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY; 1298 break; 1299 case "AUXILIARY_2": 1300 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_2; 1301 break; 1302 case "AUXILIARY_3": 1303 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_3; 1304 break; 1305 case "AUXILIARY_4": 1306 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_4; 1307 break; 1308 case "AUXILIARY_5": 1309 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_5; 1310 break; 1311 case "DISPLAY_COMPATIBILITY": 1312 type = CarOccupantZoneManager.DISPLAY_TYPE_DISPLAY_COMPATIBILITY; 1313 break; 1314 default: 1315 throwFormatErrorInDisplayMapping( 1316 "Unrecognized display type:" + entry); 1317 break; 1318 } 1319 inputTypesPerDisplay.set(type, new IntArray()); 1320 break; 1321 case "occupantZoneId": 1322 zoneId = Integer.parseInt(keyValuePair[1]); 1323 break; 1324 case "inputTypes": 1325 String[] inputStrings = keyValuePair[1].split("\\|"); 1326 for (int i = 0; i < inputStrings.length; i++) { 1327 switch (inputStrings[i].trim()) { 1328 case "ROTARY_NAVIGATION": 1329 inputTypesPerDisplay.get(type).add( 1330 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION); 1331 break; 1332 case "ROTARY_VOLUME": 1333 inputTypesPerDisplay.get(type).add( 1334 CarInputManager.INPUT_TYPE_ROTARY_VOLUME); 1335 break; 1336 case "DPAD_KEYS": 1337 inputTypesPerDisplay.get(type).add( 1338 CarInputManager.INPUT_TYPE_DPAD_KEYS); 1339 break; 1340 case "NAVIGATE_KEYS": 1341 inputTypesPerDisplay.get(type).add( 1342 CarInputManager.INPUT_TYPE_NAVIGATE_KEYS); 1343 break; 1344 case "SYSTEM_NAVIGATE_KEYS": 1345 inputTypesPerDisplay.get(type).add( 1346 CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS); 1347 break; 1348 case "CUSTOM_INPUT_EVENT": 1349 inputTypesPerDisplay.get(type).add( 1350 CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT); 1351 break; 1352 case "TOUCH_SCREEN": 1353 inputTypesPerDisplay.get(type).add( 1354 CarInputManager.INPUT_TYPE_TOUCH_SCREEN); 1355 break; 1356 case "NONE": 1357 inputTypesPerDisplay.get(type).add( 1358 CarInputManager.INPUT_TYPE_NONE); 1359 break; 1360 default: 1361 throw new IllegalArgumentException("Invalid input type: " 1362 + inputStrings[i]); 1363 } 1364 } 1365 break; 1366 default: 1367 throwFormatErrorInDisplayMapping("Unrecognized key:" + entry); 1368 break; 1369 } 1370 } 1371 1372 // Now check validity 1373 checkInputTypeNoneLocked(inputTypesPerDisplay); 1374 if (port == INVALID_PORT && uniqueId == null) { 1375 throwFormatErrorInDisplayMapping( 1376 "Missing or invalid displayPort and displayUniqueId:" + config); 1377 } 1378 if (type == CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN) { 1379 throwFormatErrorInDisplayMapping("Missing or invalid displayType:" + config); 1380 } 1381 if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 1382 throwFormatErrorInDisplayMapping("Missing or invalid occupantZoneId:" + config); 1383 } 1384 if (!mOccupantsConfig.contains(zoneId)) { 1385 throwFormatErrorInDisplayMapping( 1386 "Missing or invalid occupantZoneId:" + config); 1387 } 1388 final DisplayConfig displayConfig = new DisplayConfig(type, zoneId, 1389 inputTypesPerDisplay.get(type)); 1390 if (port != INVALID_PORT) { 1391 if (mDisplayPortConfigs.contains(port)) { 1392 throwFormatErrorInDisplayMapping("Duplicate displayPort:" + config); 1393 } 1394 mDisplayPortConfigs.put(port, displayConfig); 1395 } else { 1396 if (mDisplayUniqueIdConfigs.containsKey(uniqueId)) { 1397 throwFormatErrorInDisplayMapping("Duplicate displayUniqueId:" + config); 1398 } 1399 mDisplayUniqueIdConfigs.put(uniqueId, displayConfig); 1400 } 1401 } 1402 1403 Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); 1404 if (findDisplayConfigForDisplayLocked(defaultDisplay) == null) { 1405 int zoneForDefaultDisplay = mDriverZoneId; 1406 if (zoneForDefaultDisplay == OccupantZoneInfo.INVALID_ZONE_ID) { 1407 // No driver zone but we still need to allocate the default display to the 1st zone, 1408 // zone id 0. 1409 zoneForDefaultDisplay = 0; 1410 } 1411 Slogf.w(TAG, "No default display configuration, will assign to zone:" 1412 + zoneForDefaultDisplay); 1413 mDisplayUniqueIdConfigs.put(DisplayHelper.getUniqueId(defaultDisplay), 1414 new DisplayConfig(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 1415 zoneForDefaultDisplay, inputTypesPerDisplay.get( 1416 CarOccupantZoneManager.DISPLAY_TYPE_MAIN))); 1417 } 1418 } 1419 1420 @GuardedBy("mLock") checkInputTypeNoneLocked(SparseArray<IntArray> inputTypesPerDisplay)1421 private void checkInputTypeNoneLocked(SparseArray<IntArray> inputTypesPerDisplay) { 1422 for (int i = 0; i < inputTypesPerDisplay.size(); ++i) { 1423 IntArray inputTypes = inputTypesPerDisplay.valueAt(i); 1424 for (int j = 0; j < inputTypes.size(); ++j) { 1425 if (inputTypes.get(j) == CarInputManager.INPUT_TYPE_NONE && inputTypes.size() > 1) { 1426 throw new IllegalArgumentException("Display {" + inputTypesPerDisplay.keyAt(i) 1427 + "} has input type NONE defined along with other input types"); 1428 } 1429 } 1430 } 1431 } 1432 1433 @GuardedBy("mLock") addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info)1434 private void addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info) { 1435 OccupantConfig occupantConfig = mActiveOccupantConfigs.get(zoneId); 1436 if (occupantConfig == null) { 1437 occupantConfig = new OccupantConfig(); 1438 mActiveOccupantConfigs.put(zoneId, occupantConfig); 1439 } 1440 occupantConfig.displayInfos.add(info); 1441 } 1442 1443 @GuardedBy("mLock") handleActiveDisplaysLocked()1444 private void handleActiveDisplaysLocked() { 1445 // Clear display info for each zone in preparation for re-populating display info. 1446 for (int i = 0; i < mActiveOccupantConfigs.size(); i++) { 1447 OccupantConfig occupantConfig = mActiveOccupantConfigs.valueAt(i); 1448 occupantConfig.displayInfos.clear(); 1449 } 1450 1451 boolean hasDefaultDisplayConfig = false; 1452 boolean hasDriverZone = hasDriverZone(); 1453 for (Display display : mDisplayManager.getDisplays()) { 1454 DisplayConfig displayConfig = findDisplayConfigForDisplayLocked(display); 1455 if (displayConfig == null) { 1456 Slogf.w(TAG, "Display id:%d does not have configurations", 1457 display.getDisplayId()); 1458 continue; 1459 } 1460 if (hasDriverZone && display.getDisplayId() == Display.DEFAULT_DISPLAY) { 1461 if (displayConfig.occupantZoneId != mDriverZoneId) { 1462 throw new IllegalStateException( 1463 "Default display should be only assigned to driver zone"); 1464 } 1465 hasDefaultDisplayConfig = true; 1466 } 1467 addDisplayInfoToOccupantZoneLocked(displayConfig.occupantZoneId, 1468 new DisplayInfo(display, displayConfig.displayType)); 1469 } 1470 1471 // Remove OccupantConfig in zones without displays. 1472 for (int i = 0; i < mActiveOccupantConfigs.size(); i++) { 1473 OccupantConfig occupantConfig = mActiveOccupantConfigs.valueAt(i); 1474 if (occupantConfig.displayInfos.size() == 0) { 1475 if (DBG) { 1476 Slogf.d(TAG, "handleActiveDisplaysLocked: removing zone %d due to no display", 1477 mActiveOccupantConfigs.keyAt(i)); 1478 } 1479 mActiveOccupantConfigs.removeAt(i); 1480 } 1481 } 1482 1483 if (hasDriverZone && !hasDefaultDisplayConfig) { 1484 // This shouldn't happen, since we added the default display config in 1485 // parseDisplayConfigsLocked(). 1486 throw new IllegalStateException("Default display not assigned"); 1487 } 1488 } 1489 1490 @VisibleForTesting getCurrentUser()1491 int getCurrentUser() { 1492 return ActivityManager.getCurrentUser(); 1493 } 1494 1495 @GuardedBy("mLock") updateEnabledProfilesLocked(@serIdInt int userId)1496 private void updateEnabledProfilesLocked(@UserIdInt int userId) { 1497 mProfileUsers.clear(); 1498 List<UserHandle> profileUsers = mUserHandleHelper.getEnabledProfiles(userId); 1499 for (UserHandle profiles : profileUsers) { 1500 if (profiles.getIdentifier() != userId) { 1501 mProfileUsers.add(profiles.getIdentifier()); 1502 } 1503 } 1504 } 1505 1506 /** Returns {@code true} if user allocation has changed */ 1507 @GuardedBy("mLock") handleUserChangesLocked()1508 private boolean handleUserChangesLocked() { 1509 int currentUserId = getCurrentUser(); 1510 1511 if (mEnableProfileUserAssignmentForMultiDisplay) { 1512 updateEnabledProfilesLocked(currentUserId); 1513 } 1514 1515 boolean changed = false; 1516 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 1517 int zoneId = mActiveOccupantConfigs.keyAt(i); 1518 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 1519 OccupantZoneInfo info = mOccupantsConfig.get(zoneId); 1520 // Assign the current user to the driver zone if there is a driver zone. 1521 if (info.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER 1522 && config.userId != currentUserId) { 1523 config.userId = currentUserId; 1524 changed = true; 1525 if (DBG) { 1526 Slogf.d(TAG, "Changed driver, current user change to %d", 1527 currentUserId); 1528 } 1529 continue; 1530 } 1531 // Do not touch if the zone is un-assigned. 1532 if (config.userId == CarOccupantZoneManager.INVALID_USER_ID) { 1533 continue; 1534 } 1535 // Now it will be non-driver valid user id. 1536 if (!mCarUserService.isUserVisible(config.userId)) { 1537 if (DBG) Slogf.d(TAG, "Unassigned no longer visible user:%d", config.userId); 1538 config.userId = CarOccupantZoneManager.INVALID_USER_ID; 1539 changed = true; 1540 } 1541 } 1542 1543 return changed; 1544 } 1545 1546 @GuardedBy("mLock") handleAudioZoneChangesLocked()1547 private void handleAudioZoneChangesLocked() { 1548 for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) { 1549 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index); 1550 int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId); 1551 OccupantConfig occupantConfig = mActiveOccupantConfigs.get(occupantZoneId); 1552 if (occupantConfig == null) { 1553 //no active display for zone just continue 1554 continue; 1555 } 1556 // Found an active configuration, add audio to it. 1557 occupantConfig.audioZoneId = audioZoneId; 1558 } 1559 } 1560 syncStateAndSendConfigChangeEvent(int changeFlags)1561 private void syncStateAndSendConfigChangeEvent(int changeFlags) { 1562 // INVALID_DISPLAY is used to indicate that the displayId is not relevant to the change 1563 // event. 1564 syncStateAndSendConfigChangeEvent(changeFlags, INVALID_DISPLAY); 1565 } 1566 syncStateAndSendConfigChangeEvent(int changeFlags, int displayId)1567 private void syncStateAndSendConfigChangeEvent(int changeFlags, int displayId) { 1568 boolean updateDisplay = false; 1569 boolean updateUser = false; 1570 if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0) { 1571 updateDisplay = true; 1572 updateUser = true; 1573 } else if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0) { 1574 updateUser = true; 1575 } 1576 doSyncWithCarServiceHelper(updateDisplay, updateUser); 1577 1578 // Schedule remote callback invocation with the handler attached to the same Looper to 1579 // ensure that only one broadcast can be active at one time. 1580 mHandler.post(() -> { 1581 int n = mClientCallbacks.beginBroadcast(); 1582 for (int i = 0; i < n; i++) { 1583 ICarOccupantZoneCallback callback = mClientCallbacks.getBroadcastItem(i); 1584 try { 1585 if (displayId == INVALID_DISPLAY 1586 || getOccupantZoneForDisplayId(displayId) != null) { 1587 callback.onOccupantZoneConfigChanged(changeFlags); 1588 } 1589 } catch (RemoteException ignores) { 1590 // ignore 1591 } 1592 } 1593 mClientCallbacks.finishBroadcast(); 1594 }); 1595 } 1596 handleUserChange(boolean isUserSwitching)1597 private void handleUserChange(boolean isUserSwitching) { 1598 boolean changed = false; 1599 synchronized (mLock) { 1600 changed = handleUserChangesLocked(); 1601 } 1602 if (changed || isUserSwitching) { 1603 // Due to a timing issue, the currentUser can be System User 0 or Driver User 11. 1604 // If the previous config.userid is set to a new userId 11, when processing the 1605 // user switching event, the logic thinks it's not a user change. 1606 // So always sendConfigChangeEvent when it's a user switching case. 1607 if (!changed) { 1608 Slogf.i(TAG, "User isn't changed(%b), and isUserSwitching(%b)", 1609 changed, isUserSwitching); 1610 } 1611 syncStateAndSendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER); 1612 } 1613 } 1614 handleDisplayChange(int displayId)1615 private void handleDisplayChange(int displayId) { 1616 synchronized (mLock) { 1617 handleActiveDisplaysLocked(); 1618 // Audio zones should be re-checked for changed display 1619 handleAudioZoneChangesLocked(); 1620 // User should be re-checked for changed displays 1621 handleUserChangesLocked(); 1622 } 1623 syncStateAndSendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY, 1624 displayId); 1625 } 1626 enforcePermission(String permissionName)1627 private void enforcePermission(String permissionName) { 1628 if (mContext.checkCallingOrSelfPermission(permissionName) 1629 != PackageManager.PERMISSION_GRANTED) { 1630 throw new SecurityException("requires permission " + permissionName); 1631 } 1632 } 1633 1634 /** 1635 * Returns the supported input types for the occupant zone info and display type passed as 1636 * the argument. 1637 * 1638 * @param occupantZoneId the occupant zone id of the supported input types to find 1639 * @param displayType the display type of the supported input types to find 1640 * @return the supported input types for the occupant zone info and display type passed in as 1641 * the argument 1642 */ getSupportedInputTypes(int occupantZoneId, int displayType)1643 public int[] getSupportedInputTypes(int occupantZoneId, int displayType) { 1644 checkOccupantZone(occupantZoneId, displayType); 1645 synchronized (mLock) { 1646 // Search input type in mDisplayPortConfigs 1647 for (int i = 0; i < mDisplayPortConfigs.size(); i++) { 1648 DisplayConfig config = mDisplayPortConfigs.valueAt(i); 1649 if (config.displayType == displayType && config.occupantZoneId == occupantZoneId) { 1650 return config.inputTypes; 1651 } 1652 } 1653 // Search input type in mDisplayUniqueIdConfigs 1654 for (int i = 0; i < mDisplayUniqueIdConfigs.size(); i++) { 1655 DisplayConfig config = mDisplayUniqueIdConfigs.valueAt(i); 1656 if (config.displayType == displayType && config.occupantZoneId == occupantZoneId) { 1657 return config.inputTypes; 1658 } 1659 } 1660 } 1661 return EMPTY_INPUT_SUPPORT_TYPES; 1662 } 1663 checkOccupantZone(int occupantZoneId, int displayType)1664 private void checkOccupantZone(int occupantZoneId, int displayType) { 1665 if (Display.INVALID_DISPLAY == getDisplayForOccupant(occupantZoneId, displayType)) { 1666 throw new IllegalArgumentException("No display is associated with OccupantZoneInfo " 1667 + occupantZoneId); 1668 } 1669 } 1670 } 1671