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_SWITCHING; 21 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 import static com.android.car.util.Utils.isEventOfType; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.UserIdInt; 28 import android.app.ActivityManager; 29 import android.car.Car; 30 import android.car.CarInfoManager; 31 import android.car.CarOccupantZoneManager; 32 import android.car.CarOccupantZoneManager.DisplayTypeEnum; 33 import android.car.CarOccupantZoneManager.OccupantTypeEnum; 34 import android.car.CarOccupantZoneManager.OccupantZoneInfo; 35 import android.car.ICarOccupantZone; 36 import android.car.ICarOccupantZoneCallback; 37 import android.car.VehicleAreaSeat; 38 import android.car.builtin.os.UserManagerHelper; 39 import android.car.builtin.util.Slogf; 40 import android.car.builtin.view.DisplayHelper; 41 import android.car.media.CarAudioManager; 42 import android.car.user.CarUserManager.UserLifecycleListener; 43 import android.car.user.UserLifecycleEventFilter; 44 import android.content.ComponentName; 45 import android.content.Context; 46 import android.content.pm.PackageManager; 47 import android.content.res.Resources; 48 import android.hardware.display.DisplayManager; 49 import android.os.Handler; 50 import android.os.Looper; 51 import android.os.RemoteCallbackList; 52 import android.os.RemoteException; 53 import android.os.UserHandle; 54 import android.os.UserManager; 55 import android.util.ArrayMap; 56 import android.util.ArraySet; 57 import android.util.SparseArray; 58 import android.util.SparseIntArray; 59 import android.view.Display; 60 61 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 62 import com.android.car.internal.ICarServiceHelper; 63 import com.android.car.internal.util.IndentingPrintWriter; 64 import com.android.car.internal.util.IntArray; 65 import com.android.car.user.CarUserService; 66 import com.android.car.user.ExperimentalCarUserService; 67 import com.android.car.user.ExperimentalCarUserService.ZoneUserBindingHelper; 68 import com.android.car.user.UserHandleHelper; 69 import com.android.internal.annotations.GuardedBy; 70 import com.android.internal.annotations.VisibleForTesting; 71 72 import java.util.ArrayList; 73 import java.util.List; 74 import java.util.Objects; 75 76 /** 77 * Service to implement CarOccupantZoneManager API. 78 */ 79 public final class CarOccupantZoneService extends ICarOccupantZone.Stub 80 implements CarServiceBase { 81 82 private static final String TAG = CarLog.tagFor(CarOccupantZoneService.class); 83 private static final String ALL_COMPONENTS = "*"; 84 85 private final Object mLock = new Object(); 86 private final Context mContext; 87 private final DisplayManager mDisplayManager; 88 private final UserManager mUserManager; 89 90 private final boolean mEnableProfileUserAssignmentForMultiDisplay; 91 92 private boolean mEnableSourcePreferred; 93 private ArrayList<ComponentName> mSourcePreferredComponents; 94 95 /** 96 * Stores android user id of profile users for the current user. 97 */ 98 @GuardedBy("mLock") 99 private final ArraySet<Integer> mProfileUsers = new ArraySet<>(); 100 101 /** key: zone id */ 102 @GuardedBy("mLock") 103 private final SparseArray<OccupantZoneInfo> mOccupantsConfig = new SparseArray<>(); 104 105 @VisibleForTesting 106 static class DisplayConfig { 107 public final int displayType; 108 public final int occupantZoneId; 109 DisplayConfig(int displayType, int occupantZoneId)110 DisplayConfig(int displayType, int occupantZoneId) { 111 this.displayType = displayType; 112 this.occupantZoneId = occupantZoneId; 113 } 114 115 @Override toString()116 public String toString() { 117 // do not include type as this is only used for dump 118 StringBuilder b = new StringBuilder(64); 119 b.append("{displayType="); 120 b.append(Integer.toHexString(displayType)); 121 b.append(" occupantZoneId="); 122 b.append(occupantZoneId); 123 b.append("}"); 124 return b.toString(); 125 } 126 } 127 128 /** key: display port address */ 129 @GuardedBy("mLock") 130 private final SparseArray<DisplayConfig> mDisplayPortConfigs = new SparseArray<>(); 131 132 /** key: displayUniqueId */ 133 @GuardedBy("mLock") 134 private final ArrayMap<String, DisplayConfig> mDisplayUniqueIdConfigs = new ArrayMap<>(); 135 136 /** key: audio zone id */ 137 @GuardedBy("mLock") 138 private final SparseIntArray mAudioZoneIdToOccupantZoneIdMapping = new SparseIntArray(); 139 140 @VisibleForTesting 141 static class DisplayInfo { 142 public final Display display; 143 public final int displayType; 144 DisplayInfo(Display display, int displayType)145 DisplayInfo(Display display, int displayType) { 146 this.display = display; 147 this.displayType = displayType; 148 } 149 150 @Override toString()151 public String toString() { 152 // do not include type as this is only used for dump 153 StringBuilder b = new StringBuilder(64); 154 b.append("{displayId="); 155 b.append(display.getDisplayId()); 156 b.append(" displayType="); 157 b.append(displayType); 158 b.append("}"); 159 return b.toString(); 160 } 161 } 162 163 @VisibleForTesting 164 static class OccupantConfig { 165 public int userId = UserManagerHelper.USER_NULL; 166 public final ArrayList<DisplayInfo> displayInfos = new ArrayList<>(); 167 public int audioZoneId = CarAudioManager.INVALID_AUDIO_ZONE; 168 169 @Override toString()170 public String toString() { 171 // do not include type as this is only used for dump 172 StringBuilder b = new StringBuilder(128); 173 b.append("{userId="); 174 b.append(userId); 175 b.append(" displays="); 176 for (int i = 0; i < displayInfos.size(); i++) { 177 b.append(displayInfos.get(i).toString()); 178 } 179 b.append(" audioZoneId="); 180 if (audioZoneId != CarAudioManager.INVALID_AUDIO_ZONE) { 181 b.append(audioZoneId); 182 } else { 183 b.append("none"); 184 } 185 b.append("}"); 186 return b.toString(); 187 } 188 } 189 190 /** key : zoneId */ 191 @GuardedBy("mLock") 192 private final SparseArray<OccupantConfig> mActiveOccupantConfigs = new SparseArray<>(); 193 194 @GuardedBy("mLock") 195 private ICarServiceHelper mICarServiceHelper; 196 197 @GuardedBy("mLock") 198 private int mDriverZoneId = OccupantZoneInfo.INVALID_ZONE_ID; 199 200 @VisibleForTesting 201 final UserLifecycleListener mUserLifecycleListener = event -> { 202 if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) { 203 return; 204 } 205 Slogf.d(TAG, "onEvent(%s)", event); 206 207 handleUserChange(); 208 }; 209 210 final ExperimentalCarUserService.PassengerCallback mPassengerCallback = 211 new ExperimentalCarUserService.PassengerCallback() { 212 @Override 213 public void onPassengerStarted(@UserIdInt int passengerId, int zoneId) { 214 handlePassengerStarted(passengerId, zoneId); 215 } 216 217 @Override 218 public void onPassengerStopped(@UserIdInt int passengerId) { 219 handlePassengerStopped(passengerId); 220 } 221 }; 222 223 @VisibleForTesting 224 final DisplayManager.DisplayListener mDisplayListener = 225 new DisplayManager.DisplayListener() { 226 @Override 227 public void onDisplayAdded(int displayId) { 228 handleDisplayChange(); 229 } 230 231 @Override 232 public void onDisplayRemoved(int displayId) { 233 handleDisplayChange(); 234 } 235 236 @Override 237 public void onDisplayChanged(int displayId) { 238 // nothing to do 239 } 240 }; 241 242 private final RemoteCallbackList<ICarOccupantZoneCallback> mClientCallbacks = 243 new RemoteCallbackList<>(); 244 245 @GuardedBy("mLock") 246 private int mDriverSeat = VehicleAreaSeat.SEAT_UNKNOWN; 247 private final UserHandleHelper mUserHandleHelper; 248 CarOccupantZoneService(Context context)249 public CarOccupantZoneService(Context context) { 250 this(context, context.getSystemService(DisplayManager.class), 251 context.getSystemService(UserManager.class), 252 context.getResources().getBoolean( 253 R.bool.enableProfileUserAssignmentForMultiDisplay) 254 && context.getPackageManager().hasSystemFeature( 255 PackageManager.FEATURE_MANAGED_USERS), 256 new UserHandleHelper(context, context.getSystemService(UserManager.class))); 257 } 258 259 @VisibleForTesting CarOccupantZoneService(Context context, DisplayManager displayManager, UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay, UserHandleHelper userHandleHelper)260 public CarOccupantZoneService(Context context, DisplayManager displayManager, 261 UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay, 262 UserHandleHelper userHandleHelper) { 263 mContext = context; 264 mDisplayManager = displayManager; 265 mUserManager = userManager; 266 mEnableProfileUserAssignmentForMultiDisplay = enableProfileUserAssignmentForMultiDisplay; 267 mUserHandleHelper = userHandleHelper; 268 } 269 270 @Override init()271 public void init() { 272 // This does not require connection as binder will be passed directly. 273 Car car = new Car(mContext, /* service= */null, /* handler= */ null); 274 CarInfoManager infoManager = new CarInfoManager(car, CarLocalServices.getService( 275 CarPropertyService.class)); 276 int driverSeat = infoManager.getDriverSeat(); 277 synchronized (mLock) { 278 mDriverSeat = driverSeat; 279 parseOccupantZoneConfigsLocked(); 280 parseDisplayConfigsLocked(); 281 handleActiveDisplaysLocked(); 282 handleAudioZoneChangesLocked(); 283 handleUserChangesLocked(); 284 } 285 CarUserService userService = CarLocalServices.getService(CarUserService.class); 286 UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder() 287 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build(); 288 userService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener); 289 ExperimentalCarUserService experimentalUserService = 290 CarLocalServices.getService(ExperimentalCarUserService.class); 291 if (experimentalUserService != null) { 292 experimentalUserService.addPassengerCallback(mPassengerCallback); 293 } 294 mDisplayManager.registerDisplayListener(mDisplayListener, 295 new Handler(Looper.getMainLooper())); 296 ZoneUserBindingHelper helper = new ZoneUserBindingHelper() { 297 @Override 298 @NonNull 299 public List<OccupantZoneInfo> getOccupantZones(@OccupantTypeEnum int occupantType) { 300 List<OccupantZoneInfo> zones = new ArrayList<OccupantZoneInfo>(); 301 for (OccupantZoneInfo ozi : getAllOccupantZones()) { 302 if (ozi.occupantType == occupantType) { 303 zones.add(ozi); 304 } 305 } 306 return zones; 307 } 308 309 @Override 310 public boolean assignUserToOccupantZone(@UserIdInt int userId, int zoneId) { 311 // Check if the user is already assigned to the other zone. 312 synchronized (mLock) { 313 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 314 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 315 if (config.userId == userId && zoneId != mActiveOccupantConfigs.keyAt(i)) { 316 Slogf.w(TAG, "cannot assign user to two different zone simultaneously"); 317 return false; 318 } 319 } 320 OccupantConfig zoneConfig = mActiveOccupantConfigs.get(zoneId); 321 if (zoneConfig == null) { 322 Slogf.w(TAG, "cannot find the zone(%d)", zoneId); 323 return false; 324 } 325 if (zoneConfig.userId != UserManagerHelper.USER_NULL 326 && zoneConfig.userId != userId) { 327 Slogf.w(TAG, "other user already occupies the zone(%d)", zoneId); 328 return false; 329 } 330 zoneConfig.userId = userId; 331 return true; 332 } 333 } 334 335 @Override 336 public boolean unassignUserFromOccupantZone(@UserIdInt int userId) { 337 synchronized (mLock) { 338 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 339 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 340 if (config.userId == userId) { 341 config.userId = UserManagerHelper.USER_NULL; 342 break; 343 } 344 } 345 return true; 346 } 347 } 348 349 @Override 350 public boolean isPassengerDisplayAvailable() { 351 for (OccupantZoneInfo ozi : getAllOccupantZones()) { 352 if (getDisplayForOccupant(ozi.zoneId, 353 CarOccupantZoneManager.DISPLAY_TYPE_MAIN) != Display.INVALID_DISPLAY 354 && ozi.occupantType != CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) { 355 return true; 356 } 357 } 358 return false; 359 } 360 }; 361 if (experimentalUserService != null) { 362 experimentalUserService.setZoneUserBindingHelper(helper); 363 } 364 } 365 366 @Override release()367 public void release() { 368 mDisplayManager.unregisterDisplayListener(mDisplayListener); 369 CarUserService userService = CarLocalServices.getService(CarUserService.class); 370 userService.removeUserLifecycleListener(mUserLifecycleListener); 371 ExperimentalCarUserService experimentalUserService = 372 CarLocalServices.getService(ExperimentalCarUserService.class); 373 if (experimentalUserService != null) { 374 experimentalUserService.removePassengerCallback(mPassengerCallback); 375 } 376 synchronized (mLock) { 377 mOccupantsConfig.clear(); 378 mDisplayPortConfigs.clear(); 379 mDisplayUniqueIdConfigs.clear(); 380 mAudioZoneIdToOccupantZoneIdMapping.clear(); 381 mActiveOccupantConfigs.clear(); 382 } 383 } 384 385 /** Return cloned mOccupantsConfig for testing */ 386 @VisibleForTesting 387 @NonNull getOccupantsConfig()388 public SparseArray<OccupantZoneInfo> getOccupantsConfig() { 389 synchronized (mLock) { 390 return mOccupantsConfig.clone(); 391 } 392 } 393 394 /** Return cloned mDisplayPortConfigs for testing */ 395 @VisibleForTesting 396 @NonNull getDisplayPortConfigs()397 public SparseArray<DisplayConfig> getDisplayPortConfigs() { 398 synchronized (mLock) { 399 return mDisplayPortConfigs.clone(); 400 } 401 } 402 403 /** Return cloned mDisplayUniqueIdConfigs for testing */ 404 @VisibleForTesting 405 @NonNull getDisplayUniqueIdConfigs()406 ArrayMap<String, DisplayConfig> getDisplayUniqueIdConfigs() { 407 synchronized (mLock) { 408 return new ArrayMap<>(mDisplayUniqueIdConfigs); 409 } 410 } 411 412 /** Return cloned mAudioConfigs for testing */ 413 @VisibleForTesting 414 @NonNull getAudioConfigs()415 SparseIntArray getAudioConfigs() { 416 synchronized (mLock) { 417 return mAudioZoneIdToOccupantZoneIdMapping.clone(); 418 } 419 } 420 421 /** Return cloned mActiveOccupantConfigs for testing */ 422 @VisibleForTesting 423 @NonNull getActiveOccupantConfigs()424 public SparseArray<OccupantConfig> getActiveOccupantConfigs() { 425 synchronized (mLock) { 426 return mActiveOccupantConfigs.clone(); 427 } 428 } 429 430 @Override 431 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)432 public void dump(IndentingPrintWriter writer) { 433 writer.println("*OccupantZoneService*"); 434 synchronized (mLock) { 435 writer.println("**mOccupantsConfig**"); 436 for (int i = 0; i < mOccupantsConfig.size(); ++i) { 437 writer.println(" zoneId=" + mOccupantsConfig.keyAt(i) 438 + " info=" + mOccupantsConfig.valueAt(i)); 439 } 440 writer.println("**mDisplayConfigs**"); 441 for (int i = 0; i < mDisplayPortConfigs.size(); ++i) { 442 writer.println(" port=" + mDisplayPortConfigs.keyAt(i) 443 + " config=" + mDisplayPortConfigs.valueAt(i)); 444 } 445 for (int i = 0; i < mDisplayUniqueIdConfigs.size(); ++i) { 446 writer.println(" uniqueId=" + mDisplayUniqueIdConfigs.keyAt(i) 447 + " config=" + mDisplayUniqueIdConfigs.valueAt(i)); 448 } 449 writer.println("**mAudioZoneIdToOccupantZoneIdMapping**"); 450 for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) { 451 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index); 452 writer.println(" audioZoneId=" + Integer.toHexString(audioZoneId) 453 + " zoneId=" + mAudioZoneIdToOccupantZoneIdMapping.valueAt(index)); 454 } 455 writer.println("**mActiveOccupantConfigs**"); 456 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 457 writer.println(" zoneId=" + mActiveOccupantConfigs.keyAt(i) 458 + " config=" + mActiveOccupantConfigs.valueAt(i)); 459 } 460 writer.println("mEnableProfileUserAssignmentForMultiDisplay:" 461 + mEnableProfileUserAssignmentForMultiDisplay); 462 writer.println("mEnableSourcePreferred:" 463 + mEnableSourcePreferred); 464 writer.append("mSourcePreferredComponents: ["); 465 if (mSourcePreferredComponents != null) { 466 for (int i = 0; i < mSourcePreferredComponents.size(); ++i) { 467 if (i > 0) writer.append(' '); 468 writer.append(mSourcePreferredComponents.get(i).toString()); 469 } 470 } 471 writer.println(']'); 472 } 473 } 474 475 @Override getAllOccupantZones()476 public List<OccupantZoneInfo> getAllOccupantZones() { 477 synchronized (mLock) { 478 List<OccupantZoneInfo> infos = new ArrayList<>(); 479 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 480 int zoneId = mActiveOccupantConfigs.keyAt(i); 481 // no need for deep copy as OccupantZoneInfo itself is static. 482 infos.add(mOccupantsConfig.get(zoneId)); 483 } 484 return infos; 485 } 486 } 487 488 @Override getAllDisplaysForOccupantZone(int occupantZoneId)489 public int[] getAllDisplaysForOccupantZone(int occupantZoneId) { 490 synchronized (mLock) { 491 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 492 if (config == null) { 493 return new int[0]; 494 } 495 int[] displayIds = new int[config.displayInfos.size()]; 496 for (int i = 0; i < config.displayInfos.size(); i++) { 497 displayIds[i] = config.displayInfos.get(i).display.getDisplayId(); 498 } 499 return displayIds; 500 } 501 } 502 503 @Override getDisplayForOccupant(int occupantZoneId, int displayType)504 public int getDisplayForOccupant(int occupantZoneId, int displayType) { 505 synchronized (mLock) { 506 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 507 if (config == null) { 508 return Display.INVALID_DISPLAY; 509 } 510 for (int i = 0; i < config.displayInfos.size(); i++) { 511 if (displayType == config.displayInfos.get(i).displayType) { 512 return config.displayInfos.get(i).display.getDisplayId(); 513 } 514 } 515 } 516 return Display.INVALID_DISPLAY; 517 } 518 getAllDisplayIdsForDriver(int displayType)519 public IntArray getAllDisplayIdsForDriver(int displayType) { 520 synchronized (mLock) { 521 OccupantConfig config = mActiveOccupantConfigs.get(mDriverZoneId); 522 if (config == null) { 523 return new IntArray(0); 524 } 525 IntArray displayIds = new IntArray(config.displayInfos.size()); 526 Slogf.d(TAG, "getAllDisplayIdsForDriver: displayInfos=" + config.displayInfos); 527 for (int i = 0; i < config.displayInfos.size(); i++) { 528 DisplayInfo displayInfo = config.displayInfos.get(i); 529 if (displayInfo.displayType == displayType) { 530 displayIds.add(displayInfo.display.getDisplayId()); 531 } 532 } 533 return displayIds; 534 } 535 } 536 537 @Override getDisplayIdForDriver(@isplayTypeEnum int displayType)538 public int getDisplayIdForDriver(@DisplayTypeEnum int displayType) { 539 enforcePermission(Car.ACCESS_PRIVATE_DISPLAY_ID); 540 synchronized (mLock) { 541 int driverUserId = getDriverUserId(); 542 DisplayInfo displayInfo = findDisplayForDriverLocked(driverUserId, displayType); 543 if (displayInfo == null) { 544 return Display.INVALID_DISPLAY; 545 } 546 return displayInfo.display.getDisplayId(); 547 } 548 } 549 550 @GuardedBy("mLock") 551 @Nullable findDisplayForDriverLocked(int driverUserId, @DisplayTypeEnum int displayType)552 private DisplayInfo findDisplayForDriverLocked(int driverUserId, 553 @DisplayTypeEnum int displayType) { 554 for (OccupantZoneInfo zoneInfo : getAllOccupantZones()) { 555 if (zoneInfo.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) { 556 OccupantConfig config = mActiveOccupantConfigs.get(zoneInfo.zoneId); 557 if (config == null) { 558 //No active display for zone, just continue... 559 continue; 560 } 561 562 if (config.userId == driverUserId) { 563 for (DisplayInfo displayInfo : config.displayInfos) { 564 if (displayInfo.displayType == displayType) { 565 return displayInfo; 566 } 567 } 568 } 569 } 570 } 571 return null; 572 } 573 574 @Override getAudioZoneIdForOccupant(int occupantZoneId)575 public int getAudioZoneIdForOccupant(int occupantZoneId) { 576 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 577 synchronized (mLock) { 578 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 579 if (config != null) { 580 return config.audioZoneId; 581 } 582 // check if the occupant id exist at all 583 if (!mOccupantsConfig.contains(occupantZoneId)) { 584 return CarAudioManager.INVALID_AUDIO_ZONE; 585 } 586 // Exist but not active 587 return getAudioZoneIdForOccupantLocked(occupantZoneId); 588 } 589 } 590 591 @GuardedBy("mLock") getAudioZoneIdForOccupantLocked(int occupantZoneId)592 private int getAudioZoneIdForOccupantLocked(int occupantZoneId) { 593 for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) { 594 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index); 595 if (occupantZoneId == mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId)) { 596 return audioZoneId; 597 } 598 } 599 return CarAudioManager.INVALID_AUDIO_ZONE; 600 } 601 602 @Override getOccupantForAudioZoneId(int audioZoneId)603 public CarOccupantZoneManager.OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) { 604 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 605 synchronized (mLock) { 606 int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId, 607 OccupantZoneInfo.INVALID_ZONE_ID); 608 if (occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 609 return null; 610 } 611 // To support headless zones return the occupant configuration. 612 return mOccupantsConfig.get(occupantZoneId); 613 } 614 } 615 616 @GuardedBy("mLock") 617 @Nullable findDisplayConfigForDisplayIdLocked(int displayId)618 private DisplayConfig findDisplayConfigForDisplayIdLocked(int displayId) { 619 Display display = mDisplayManager.getDisplay(displayId); 620 if (display == null) { 621 return null; 622 } 623 return findDisplayConfigForDisplayLocked(display); 624 } 625 626 @GuardedBy("mLock") 627 @Nullable findDisplayConfigForDisplayLocked(Display display)628 private DisplayConfig findDisplayConfigForDisplayLocked(Display display) { 629 int portAddress = DisplayHelper.getPhysicalPort(display); 630 if (portAddress != INVALID_PORT) { 631 DisplayConfig config = mDisplayPortConfigs.get(portAddress); 632 if (config != null) { 633 return config; 634 } 635 } 636 return mDisplayUniqueIdConfigs.get(DisplayHelper.getUniqueId(display)); 637 } 638 639 @Override getDisplayType(int displayId)640 public int getDisplayType(int displayId) { 641 synchronized (mLock) { 642 DisplayConfig config = findDisplayConfigForDisplayIdLocked(displayId); 643 if (config != null) { 644 return config.displayType; 645 } 646 } 647 return CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN; 648 } 649 650 @Override getUserForOccupant(int occupantZoneId)651 public int getUserForOccupant(int occupantZoneId) { 652 synchronized (mLock) { 653 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 654 if (config == null) { 655 return UserManagerHelper.USER_NULL; 656 } 657 return config.userId; 658 } 659 } 660 661 @Override getOccupantZoneIdForUserId(int userId)662 public int getOccupantZoneIdForUserId(int userId) { 663 synchronized (mLock) { 664 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 665 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 666 if (config.userId == userId) { 667 return mActiveOccupantConfigs.keyAt(i); 668 } 669 } 670 Slogf.w(TAG, "Could not find occupantZoneId for userId%d returning invalid " 671 + "occupant zone id %d", userId, OccupantZoneInfo.INVALID_ZONE_ID); 672 return OccupantZoneInfo.INVALID_ZONE_ID; 673 } 674 } 675 676 /** 677 * returns the current driver user id. 678 */ getDriverUserId()679 public @UserIdInt int getDriverUserId() { 680 return getCurrentUser(); 681 } 682 683 /** 684 * Sets the mapping for audio zone id to occupant zone id. 685 * 686 * @param audioZoneIdToOccupantZoneMapping map for audio zone id, where key is the audio zone id 687 * and value is the occupant zone id. 688 */ setAudioZoneIdsForOccupantZoneIds( @onNull SparseIntArray audioZoneIdToOccupantZoneMapping)689 public void setAudioZoneIdsForOccupantZoneIds( 690 @NonNull SparseIntArray audioZoneIdToOccupantZoneMapping) { 691 Objects.requireNonNull(audioZoneIdToOccupantZoneMapping, 692 "audioZoneIdToOccupantZoneMapping can not be null"); 693 synchronized (mLock) { 694 validateOccupantZoneIdsLocked(audioZoneIdToOccupantZoneMapping); 695 mAudioZoneIdToOccupantZoneIdMapping.clear(); 696 for (int index = 0; index < audioZoneIdToOccupantZoneMapping.size(); index++) { 697 int audioZoneId = audioZoneIdToOccupantZoneMapping.keyAt(index); 698 mAudioZoneIdToOccupantZoneIdMapping.put(audioZoneId, 699 audioZoneIdToOccupantZoneMapping.get(audioZoneId)); 700 } 701 //If there are any active displays for the zone send change event 702 handleAudioZoneChangesLocked(); 703 } 704 sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO); 705 } 706 707 @GuardedBy("mLock") validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping)708 private void validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping) { 709 for (int i = 0; i < audioZoneIdToOccupantZoneMapping.size(); i++) { 710 int occupantZoneId = 711 audioZoneIdToOccupantZoneMapping.get(audioZoneIdToOccupantZoneMapping.keyAt(i)); 712 if (!mOccupantsConfig.contains(occupantZoneId)) { 713 throw new IllegalArgumentException("occupantZoneId " + occupantZoneId 714 + " does not exist."); 715 } 716 } 717 } 718 719 @Override registerCallback(ICarOccupantZoneCallback callback)720 public void registerCallback(ICarOccupantZoneCallback callback) { 721 mClientCallbacks.register(callback); 722 } 723 724 @Override unregisterCallback(ICarOccupantZoneCallback callback)725 public void unregisterCallback(ICarOccupantZoneCallback callback) { 726 mClientCallbacks.unregister(callback); 727 } 728 729 @Override assignProfileUserToOccupantZone(int occupantZoneId, int userId)730 public boolean assignProfileUserToOccupantZone(int occupantZoneId, int userId) { 731 enforcePermission(android.Manifest.permission.MANAGE_USERS); 732 if (!mEnableProfileUserAssignmentForMultiDisplay) { 733 throw new IllegalStateException("feature not enabled"); 734 } 735 int currentUser = getCurrentUser(); 736 737 synchronized (mLock) { 738 if (occupantZoneId == mDriverZoneId) { 739 throw new IllegalArgumentException("Driver zone cannot have profile user"); 740 } 741 updateEnabledProfilesLocked(currentUser); 742 743 if (!mProfileUsers.contains(userId) && userId != UserManagerHelper.USER_NULL) { 744 // current user can change while this call is happening, so return false rather 745 // than throwing exception 746 Slogf.w(TAG, "Invalid profile user id: %d", userId); 747 return false; 748 } 749 if (!mUserManager.isUserRunning(UserHandle.of(userId))) { 750 Slogf.w(TAG, "User%d is not running.", userId); 751 return false; 752 } 753 OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId); 754 if (config == null) { 755 throw new IllegalArgumentException("Invalid occupantZoneId:" + occupantZoneId); 756 } 757 if (config.userId == userId && userId != UserManagerHelper.USER_NULL) { 758 Slogf.w(TAG, "assignProfileUserToOccupantZone zone:%d already set to user:%", 759 occupantZoneId, userId); 760 return true; 761 } 762 if (userId == UserManagerHelper.USER_NULL) { 763 config.userId = currentUser; 764 } else { 765 config.userId = userId; 766 } 767 } 768 sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER); 769 return true; 770 } 771 772 /** 773 * Sets {@code ICarServiceHelper}. 774 */ setCarServiceHelper(ICarServiceHelper helper)775 public void setCarServiceHelper(ICarServiceHelper helper) { 776 doSyncWithCarServiceHelper(helper, /* updateDisplay= */ true, /* updateUser= */ true, 777 /* updateConfig= */ true); 778 } 779 doSyncWithCarServiceHelper(@ullable ICarServiceHelper helper, boolean updateDisplay, boolean updateUser, boolean updateConfig)780 private void doSyncWithCarServiceHelper(@Nullable ICarServiceHelper helper, 781 boolean updateDisplay, boolean updateUser, boolean updateConfig) { 782 int[] passengerDisplays = null; 783 ArrayMap<Integer, IntArray> allowlists = null; 784 ICarServiceHelper helperToUse = helper; 785 synchronized (mLock) { 786 if (helper == null) { 787 if (mICarServiceHelper == null) { // helper not set yet. 788 return; 789 } 790 helperToUse = mICarServiceHelper; 791 } else { 792 mICarServiceHelper = helper; 793 } 794 if (updateDisplay) { 795 passengerDisplays = getAllActivePassengerDisplaysLocked(); 796 } 797 if (updateUser) { 798 allowlists = createDisplayAllowlistsLocked(); 799 } 800 } 801 if (updateDisplay) { 802 updatePassengerDisplays(helperToUse, passengerDisplays); 803 } 804 if (updateUser) { 805 updateUserAssignmentForDisplays(helperToUse, allowlists); 806 } 807 if (updateConfig) { 808 Resources res = mContext.getResources(); 809 String[] components = res.getStringArray(R.array.config_sourcePreferredComponents); 810 updateSourcePreferredComponents(helperToUse, components); 811 } 812 } 813 814 @GuardedBy("mLock") getAllActivePassengerDisplaysLocked()815 private int[] getAllActivePassengerDisplaysLocked() { 816 IntArray displays = new IntArray(); 817 for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) { 818 int zoneId = mActiveOccupantConfigs.keyAt(j); 819 if (zoneId == mDriverZoneId) { 820 continue; 821 } 822 OccupantConfig config = mActiveOccupantConfigs.valueAt(j); 823 for (int i = 0; i < config.displayInfos.size(); i++) { 824 displays.add(config.displayInfos.get(i).display.getDisplayId()); 825 } 826 } 827 return displays.toArray(); 828 } 829 updatePassengerDisplays(ICarServiceHelper helper, int[] passengerDisplayIds)830 private void updatePassengerDisplays(ICarServiceHelper helper, int[] passengerDisplayIds) { 831 if (passengerDisplayIds == null) { 832 return; 833 } 834 try { 835 helper.setPassengerDisplays(passengerDisplayIds); 836 } catch (RemoteException e) { 837 Slogf.e(TAG, "ICarServiceHelper.setPassengerDisplays failed", e); 838 } 839 } 840 updateSourcePreferredComponents(ICarServiceHelper helper, String[] components)841 private void updateSourcePreferredComponents(ICarServiceHelper helper, String[] components) { 842 boolean enableSourcePreferred; 843 ArrayList<ComponentName> componentNames = null; 844 if (components == null || components.length == 0) { 845 enableSourcePreferred = false; 846 Slogf.i(TAG, "CarLaunchParamsModifier: disable source-preferred"); 847 } else if (components.length == 1 && components[0].equals(ALL_COMPONENTS)) { 848 enableSourcePreferred = true; 849 Slogf.i(TAG, "CarLaunchParamsModifier: enable source-preferred for all Components"); 850 } else { 851 componentNames = new ArrayList<>((components.length)); 852 for (String item : components) { 853 ComponentName name = ComponentName.unflattenFromString(item); 854 if (name == null) { 855 Slogf.e(TAG, "CarLaunchParamsModifier: Wrong ComponentName=" + item); 856 return; 857 } 858 componentNames.add(name); 859 } 860 enableSourcePreferred = true; 861 } 862 try { 863 helper.setSourcePreferredComponents(enableSourcePreferred, componentNames); 864 mEnableSourcePreferred = enableSourcePreferred; 865 mSourcePreferredComponents = componentNames; 866 } catch (RemoteException e) { 867 Slogf.e(TAG, "ICarServiceHelper.setSourcePreferredComponents failed"); 868 } 869 } 870 871 @GuardedBy("mLock") createDisplayAllowlistsLocked()872 private ArrayMap<Integer, IntArray> createDisplayAllowlistsLocked() { 873 ArrayMap<Integer, IntArray> allowlists = new ArrayMap<>(); 874 for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) { 875 int zoneId = mActiveOccupantConfigs.keyAt(j); 876 if (zoneId == mDriverZoneId) { 877 continue; 878 } 879 OccupantConfig config = mActiveOccupantConfigs.valueAt(j); 880 if (config.displayInfos.isEmpty()) { 881 continue; 882 } 883 // user like driver can have multiple zones assigned, so add them all. 884 IntArray displays = allowlists.get(config.userId); 885 if (displays == null) { 886 displays = new IntArray(); 887 allowlists.put(config.userId, displays); 888 } 889 for (int i = 0; i < config.displayInfos.size(); i++) { 890 displays.add(config.displayInfos.get(i).display.getDisplayId()); 891 } 892 } 893 return allowlists; 894 } 895 updateUserAssignmentForDisplays(ICarServiceHelper helper, ArrayMap<Integer, IntArray> allowlists)896 private void updateUserAssignmentForDisplays(ICarServiceHelper helper, 897 ArrayMap<Integer, IntArray> allowlists) { 898 if (allowlists == null || allowlists.isEmpty()) { 899 return; 900 } 901 try { 902 for (int i = 0; i < allowlists.size(); i++) { 903 int userId = allowlists.keyAt(i); 904 helper.setDisplayAllowlistForUser(userId, allowlists.valueAt(i).toArray()); 905 } 906 } catch (RemoteException e) { 907 Slogf.e(TAG, "ICarServiceHelper.setDisplayAllowlistForUser failed", e); 908 } 909 } 910 throwFormatErrorInOccupantZones(String msg)911 private void throwFormatErrorInOccupantZones(String msg) { 912 throw new RuntimeException("Format error in config_occupant_zones resource:" + msg); 913 } 914 915 // For overriding in test 916 @VisibleForTesting getDriverSeat()917 int getDriverSeat() { 918 synchronized (mLock) { 919 return mDriverSeat; 920 } 921 } 922 923 @GuardedBy("mLock") parseOccupantZoneConfigsLocked()924 private void parseOccupantZoneConfigsLocked() { 925 final Resources res = mContext.getResources(); 926 // examples: 927 // <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item> 928 // <item>occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1, 929 // searSide=oppositeDriver</item> 930 boolean hasDriver = false; 931 int driverSeat = getDriverSeat(); 932 int driverSeatSide = VehicleAreaSeat.SIDE_LEFT; // default LHD : Left Hand Drive 933 if (driverSeat == VehicleAreaSeat.SEAT_ROW_1_RIGHT) { 934 driverSeatSide = VehicleAreaSeat.SIDE_RIGHT; 935 } 936 int maxZoneId = OccupantZoneInfo.INVALID_ZONE_ID; 937 for (String config : res.getStringArray(R.array.config_occupant_zones)) { 938 int zoneId = OccupantZoneInfo.INVALID_ZONE_ID; 939 int type = CarOccupantZoneManager.OCCUPANT_TYPE_INVALID; 940 int seatRow = 0; // invalid row 941 int seatSide = VehicleAreaSeat.SIDE_LEFT; 942 String[] entries = config.split(","); 943 for (String entry : entries) { 944 String[] keyValuePair = entry.split("="); 945 if (keyValuePair.length != 2) { 946 throwFormatErrorInOccupantZones("No key/value pair:" + entry); 947 } 948 switch (keyValuePair[0]) { 949 case "occupantZoneId": 950 zoneId = Integer.parseInt(keyValuePair[1]); 951 break; 952 case "occupantType": 953 switch (keyValuePair[1]) { 954 case "DRIVER": 955 type = CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER; 956 break; 957 case "FRONT_PASSENGER": 958 type = CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER; 959 break; 960 case "REAR_PASSENGER": 961 type = CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER; 962 break; 963 default: 964 throwFormatErrorInOccupantZones("Unrecognized type:" + entry); 965 break; 966 } 967 break; 968 case "seatRow": 969 seatRow = Integer.parseInt(keyValuePair[1]); 970 break; 971 case "seatSide": 972 switch (keyValuePair[1]) { 973 case "driver": 974 seatSide = driverSeatSide; 975 break; 976 case "oppositeDriver": 977 seatSide = -driverSeatSide; 978 break; 979 case "left": 980 seatSide = VehicleAreaSeat.SIDE_LEFT; 981 break; 982 case "center": 983 seatSide = VehicleAreaSeat.SIDE_CENTER; 984 break; 985 case "right": 986 seatSide = VehicleAreaSeat.SIDE_RIGHT; 987 break; 988 default: 989 throwFormatErrorInOccupantZones("Unregognized seatSide:" + entry); 990 break; 991 992 } 993 break; 994 default: 995 throwFormatErrorInOccupantZones("Unrecognized key:" + entry); 996 break; 997 } 998 } 999 if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 1000 throwFormatErrorInOccupantZones("Missing zone id:" + config); 1001 } 1002 if (zoneId > maxZoneId) { 1003 maxZoneId = zoneId; 1004 } 1005 if (type == CarOccupantZoneManager.OCCUPANT_TYPE_INVALID) { 1006 throwFormatErrorInOccupantZones("Missing type:" + config); 1007 } 1008 if (type == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) { 1009 if (hasDriver) { 1010 throwFormatErrorInOccupantZones("Multiple driver:" + config); 1011 } else { 1012 hasDriver = true; 1013 mDriverZoneId = zoneId; 1014 } 1015 } 1016 int seat = VehicleAreaSeat.fromRowAndSide(seatRow, seatSide); 1017 if (seat == VehicleAreaSeat.SEAT_UNKNOWN) { 1018 throwFormatErrorInOccupantZones("Invalid seat:" + config); 1019 } 1020 OccupantZoneInfo info = new OccupantZoneInfo(zoneId, type, seat); 1021 if (mOccupantsConfig.contains(zoneId)) { 1022 throwFormatErrorInOccupantZones("Duplicate zone id:" + config); 1023 } 1024 mOccupantsConfig.put(zoneId, info); 1025 } 1026 if (!hasDriver) { 1027 maxZoneId++; 1028 mDriverZoneId = maxZoneId; 1029 Slogf.w(TAG, "No driver zone, add one:%d", mDriverZoneId); 1030 OccupantZoneInfo info = new OccupantZoneInfo(mDriverZoneId, 1031 CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER, getDriverSeat()); 1032 mOccupantsConfig.put(mDriverZoneId, info); 1033 } 1034 } 1035 throwFormatErrorInDisplayMapping(String msg)1036 private void throwFormatErrorInDisplayMapping(String msg) { 1037 throw new RuntimeException( 1038 "Format error in config_occupant_display_mapping resource:" + msg); 1039 } 1040 1041 @GuardedBy("mLock") parseDisplayConfigsLocked()1042 private void parseDisplayConfigsLocked() { 1043 final Resources res = mContext.getResources(); 1044 // examples: 1045 // <item>displayPort=0,displayType=MAIN,occupantZoneId=0</item> 1046 // <item>displayPort=1,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0</item> 1047 for (String config : res.getStringArray(R.array.config_occupant_display_mapping)) { 1048 int port = INVALID_PORT; 1049 String uniqueId = null; 1050 int type = CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN; 1051 int zoneId = OccupantZoneInfo.INVALID_ZONE_ID; 1052 String[] entries = config.split(","); 1053 for (String entry : entries) { 1054 String[] keyValuePair = entry.split("="); 1055 if (keyValuePair.length != 2) { 1056 throwFormatErrorInDisplayMapping("No key/value pair:" + entry); 1057 } 1058 switch (keyValuePair[0]) { 1059 case "displayPort": 1060 port = Integer.parseInt(keyValuePair[1]); 1061 break; 1062 case "displayUniqueId": 1063 uniqueId = keyValuePair[1]; 1064 break; 1065 case "displayType": 1066 switch (keyValuePair[1]) { 1067 case "MAIN": 1068 type = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1069 break; 1070 case "INSTRUMENT_CLUSTER": 1071 type = CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER; 1072 break; 1073 case "HUD": 1074 type = CarOccupantZoneManager.DISPLAY_TYPE_HUD; 1075 break; 1076 case "INPUT": 1077 type = CarOccupantZoneManager.DISPLAY_TYPE_INPUT; 1078 break; 1079 case "AUXILIARY": 1080 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY; 1081 break; 1082 default: 1083 throwFormatErrorInDisplayMapping( 1084 "Unrecognized display type:" + entry); 1085 break; 1086 } 1087 break; 1088 case "occupantZoneId": 1089 zoneId = Integer.parseInt(keyValuePair[1]); 1090 break; 1091 default: 1092 throwFormatErrorInDisplayMapping("Unrecognized key:" + entry); 1093 break; 1094 1095 } 1096 } 1097 // Now check validity 1098 if (port == INVALID_PORT && uniqueId == null) { 1099 throwFormatErrorInDisplayMapping( 1100 "Missing or invalid displayPort and displayUniqueId:" + config); 1101 } 1102 1103 if (type == CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN) { 1104 throwFormatErrorInDisplayMapping("Missing or invalid displayType:" + config); 1105 } 1106 if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 1107 throwFormatErrorInDisplayMapping("Missing or invalid occupantZoneId:" + config); 1108 } 1109 if (!mOccupantsConfig.contains(zoneId)) { 1110 throwFormatErrorInDisplayMapping( 1111 "Missing or invalid occupantZoneId:" + config); 1112 } 1113 DisplayConfig displayConfig = new DisplayConfig(type, zoneId); 1114 if (port != INVALID_PORT) { 1115 if (mDisplayPortConfigs.contains(port)) { 1116 throwFormatErrorInDisplayMapping("Duplicate displayPort:" + config); 1117 } 1118 mDisplayPortConfigs.put(port, displayConfig); 1119 } else { 1120 if (mDisplayUniqueIdConfigs.containsKey(uniqueId)) { 1121 throwFormatErrorInDisplayMapping("Duplicate displayUniqueId:" + config); 1122 } 1123 mDisplayUniqueIdConfigs.put(uniqueId, displayConfig); 1124 } 1125 } 1126 Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); 1127 if (findDisplayConfigForDisplayLocked(defaultDisplay) == null) { 1128 Slogf.w(TAG, "No default display configuration, will assign to driver zone"); 1129 mDisplayUniqueIdConfigs.put(DisplayHelper.getUniqueId(defaultDisplay), 1130 new DisplayConfig(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mDriverZoneId)); 1131 } 1132 } 1133 1134 @GuardedBy("mLock") addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info)1135 private void addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info) { 1136 OccupantConfig occupantConfig = mActiveOccupantConfigs.get(zoneId); 1137 if (occupantConfig == null) { 1138 occupantConfig = new OccupantConfig(); 1139 mActiveOccupantConfigs.put(zoneId, occupantConfig); 1140 } 1141 occupantConfig.displayInfos.add(info); 1142 } 1143 1144 @GuardedBy("mLock") handleActiveDisplaysLocked()1145 private void handleActiveDisplaysLocked() { 1146 mActiveOccupantConfigs.clear(); 1147 boolean hasDefaultDisplayConfig = false; 1148 for (Display display : mDisplayManager.getDisplays()) { 1149 DisplayConfig displayConfig = findDisplayConfigForDisplayLocked(display); 1150 if (displayConfig == null) { 1151 Slogf.w(TAG, "Display id:%d does not have configurations", 1152 display.getDisplayId()); 1153 continue; 1154 } 1155 if (display.getDisplayId() == Display.DEFAULT_DISPLAY) { 1156 if (displayConfig.occupantZoneId != mDriverZoneId) { 1157 throw new IllegalStateException( 1158 "Default display should be only assigned to driver zone"); 1159 } 1160 hasDefaultDisplayConfig = true; 1161 } 1162 addDisplayInfoToOccupantZoneLocked(displayConfig.occupantZoneId, 1163 new DisplayInfo(display, displayConfig.displayType)); 1164 } 1165 if (!hasDefaultDisplayConfig) { 1166 // This shouldn't happen, since we added the default display config in 1167 // parseDisplayConfigsLocked(). 1168 throw new IllegalStateException("Default display not assigned"); 1169 } 1170 } 1171 1172 @VisibleForTesting getCurrentUser()1173 int getCurrentUser() { 1174 return ActivityManager.getCurrentUser(); 1175 } 1176 1177 @GuardedBy("mLock") updateEnabledProfilesLocked(int userId)1178 private void updateEnabledProfilesLocked(int userId) { 1179 mProfileUsers.clear(); 1180 List<UserHandle> profileUsers = mUserHandleHelper.getEnabledProfiles(userId); 1181 for (UserHandle profiles : profileUsers) { 1182 if (profiles.getIdentifier() != userId) { 1183 mProfileUsers.add(profiles.getIdentifier()); 1184 } 1185 } 1186 } 1187 1188 @GuardedBy("mLock") handleUserChangesLocked()1189 private void handleUserChangesLocked() { 1190 int driverUserId = getCurrentUser(); 1191 1192 if (mEnableProfileUserAssignmentForMultiDisplay) { 1193 updateEnabledProfilesLocked(driverUserId); 1194 } 1195 1196 for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) { 1197 int zoneId = mActiveOccupantConfigs.keyAt(i); 1198 OccupantConfig config = mActiveOccupantConfigs.valueAt(i); 1199 // mProfileUsers empty if not supported 1200 if (mProfileUsers.contains(config.userId)) { 1201 Slogf.i(TAG, "Profile user:%d already assigned for occupant zone:%d", 1202 config.userId, zoneId); 1203 } else { 1204 config.userId = driverUserId; 1205 } 1206 } 1207 } 1208 1209 @GuardedBy("mLock") handleAudioZoneChangesLocked()1210 private void handleAudioZoneChangesLocked() { 1211 for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) { 1212 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index); 1213 int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId); 1214 OccupantConfig occupantConfig = mActiveOccupantConfigs.get(occupantZoneId); 1215 if (occupantConfig == null) { 1216 //no active display for zone just continue 1217 continue; 1218 } 1219 // Found an active configuration, add audio to it. 1220 occupantConfig.audioZoneId = audioZoneId; 1221 } 1222 } 1223 sendConfigChangeEvent(int changeFlags)1224 private void sendConfigChangeEvent(int changeFlags) { 1225 boolean updateDisplay = false; 1226 boolean updateUser = false; 1227 if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0) { 1228 updateDisplay = true; 1229 updateUser = true; 1230 } else if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0) { 1231 updateUser = true; 1232 } 1233 doSyncWithCarServiceHelper(/* helper= */ null, updateDisplay, updateUser, 1234 /* updateConfig= */ false); 1235 1236 final int n = mClientCallbacks.beginBroadcast(); 1237 for (int i = 0; i < n; i++) { 1238 ICarOccupantZoneCallback callback = mClientCallbacks.getBroadcastItem(i); 1239 try { 1240 callback.onOccupantZoneConfigChanged(changeFlags); 1241 } catch (RemoteException ignores) { 1242 // ignore 1243 } 1244 } 1245 mClientCallbacks.finishBroadcast(); 1246 } 1247 handleUserChange()1248 private void handleUserChange() { 1249 synchronized (mLock) { 1250 handleUserChangesLocked(); 1251 } 1252 sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER); 1253 } 1254 handlePassengerStarted(@serIdInt int passengerId, int zoneId)1255 private void handlePassengerStarted(@UserIdInt int passengerId, int zoneId) { 1256 sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER); 1257 } 1258 handlePassengerStopped(@serIdInt int passengerId)1259 private void handlePassengerStopped(@UserIdInt int passengerId) { 1260 sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER); 1261 } 1262 handleDisplayChange()1263 private void handleDisplayChange() { 1264 synchronized (mLock) { 1265 handleActiveDisplaysLocked(); 1266 //audio zones should be re-checked for changed display 1267 handleAudioZoneChangesLocked(); 1268 // user should be re-checked for changed displays 1269 handleUserChangesLocked(); 1270 } 1271 sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY); 1272 } 1273 enforcePermission(String permissionName)1274 private void enforcePermission(String permissionName) { 1275 if (mContext.checkCallingOrSelfPermission(permissionName) 1276 != PackageManager.PERMISSION_GRANTED) { 1277 throw new SecurityException("requires permission " + permissionName); 1278 } 1279 } 1280 } 1281