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