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