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 android.car; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.annotation.UserIdInt; 27 import android.car.annotation.AddedInOrBefore; 28 import android.car.builtin.os.UserManagerHelper; 29 import android.hardware.display.DisplayManager; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.Parcel; 35 import android.os.Parcelable; 36 import android.os.RemoteException; 37 import android.os.UserHandle; 38 import android.util.Log; 39 import android.view.Display; 40 41 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import java.lang.annotation.ElementType; 45 import java.lang.annotation.Retention; 46 import java.lang.annotation.RetentionPolicy; 47 import java.lang.annotation.Target; 48 import java.lang.ref.WeakReference; 49 import java.util.ArrayList; 50 import java.util.Collections; 51 import java.util.List; 52 import java.util.concurrent.CopyOnWriteArrayList; 53 54 /** 55 * API to get information on displays and users in the car. 56 */ 57 public class CarOccupantZoneManager extends CarManagerBase { 58 59 private static final String TAG = CarOccupantZoneManager.class.getSimpleName(); 60 61 /** Display type is not known. In some system, some displays may be just public display without 62 * any additional information and such displays will be treated as unknown. 63 */ 64 @AddedInOrBefore(majorVersion = 33) 65 public static final int DISPLAY_TYPE_UNKNOWN = 0; 66 67 /** Main display users are interacting with. UI for the user will be launched to this display by 68 * default. {@link Display#DEFAULT_DISPLAY} will be always have this type. But there can be 69 * multiple of this type as each passenger can have their own main display. 70 */ 71 @AddedInOrBefore(majorVersion = 33) 72 public static final int DISPLAY_TYPE_MAIN = 1; 73 74 /** Instrument cluster display. This may exist only for driver. */ 75 @AddedInOrBefore(majorVersion = 33) 76 public static final int DISPLAY_TYPE_INSTRUMENT_CLUSTER = 2; 77 78 /** Head Up Display. This may exist only for driver. */ 79 @AddedInOrBefore(majorVersion = 33) 80 public static final int DISPLAY_TYPE_HUD = 3; 81 82 /** Dedicated display for showing IME for {@link #DISPLAY_TYPE_MAIN} */ 83 @AddedInOrBefore(majorVersion = 33) 84 public static final int DISPLAY_TYPE_INPUT = 4; 85 86 /** Auxiliary display which can provide additional screen for {@link #DISPLAY_TYPE_MAIN}. 87 * Activity running in {@link #DISPLAY_TYPE_MAIN} may use {@link android.app.Presentation} to 88 * show additional information. 89 */ 90 @AddedInOrBefore(majorVersion = 33) 91 public static final int DISPLAY_TYPE_AUXILIARY = 5; 92 93 /** @hide */ 94 @Retention(RetentionPolicy.SOURCE) 95 @IntDef(prefix = "DISPLAY_TYPE_", value = { 96 DISPLAY_TYPE_UNKNOWN, 97 DISPLAY_TYPE_MAIN, 98 DISPLAY_TYPE_INSTRUMENT_CLUSTER, 99 DISPLAY_TYPE_HUD, 100 DISPLAY_TYPE_INPUT, 101 DISPLAY_TYPE_AUXILIARY, 102 }) 103 @Target({ElementType.TYPE_USE}) 104 public @interface DisplayTypeEnum {} 105 106 /** @hide */ 107 @AddedInOrBefore(majorVersion = 33) 108 public static final int OCCUPANT_TYPE_INVALID = -1; 109 110 /** Represents driver. There can be only one driver for the system. */ 111 @AddedInOrBefore(majorVersion = 33) 112 public static final int OCCUPANT_TYPE_DRIVER = 0; 113 114 /** Represents front passengers who sits in front side of car. Most cars will have only 115 * one passenger of this type but this can be multiple. */ 116 @AddedInOrBefore(majorVersion = 33) 117 public static final int OCCUPANT_TYPE_FRONT_PASSENGER = 1; 118 119 /** Represents passengers in rear seats. There can be multiple passengers of this type. */ 120 @AddedInOrBefore(majorVersion = 33) 121 public static final int OCCUPANT_TYPE_REAR_PASSENGER = 2; 122 123 /** @hide */ 124 @Retention(RetentionPolicy.SOURCE) 125 @IntDef(prefix = "OCCUPANT_TYPE_", value = { 126 OCCUPANT_TYPE_DRIVER, 127 OCCUPANT_TYPE_FRONT_PASSENGER, 128 OCCUPANT_TYPE_REAR_PASSENGER, 129 }) 130 @Target({ElementType.TYPE_USE}) 131 public @interface OccupantTypeEnum {} 132 133 /** 134 * Represents an occupant zone in a car. 135 * 136 * <p>Each occupant does not necessarily represent single person but it is for mapping to one 137 * set of displays. For example, for display located in center rear seat, both left and right 138 * side passengers may use it but it is abstracted as a single occupant zone.</p> 139 */ 140 public static final class OccupantZoneInfo implements Parcelable { 141 /** @hide */ 142 @AddedInOrBefore(majorVersion = 33) 143 public static final int INVALID_ZONE_ID = -1; 144 /** 145 * This is an unique id to distinguish each occupant zone. 146 * 147 * <p>This can be helpful to distinguish different zones when {@link #occupantType} and 148 * {@link #seat} are the same for multiple occupant / passenger zones.</p> 149 * 150 * <p>This id will remain the same for the same zone across configuration changes like 151 * user switching or display changes</p> 152 */ 153 @AddedInOrBefore(majorVersion = 33) 154 public int zoneId; 155 /** Represents type of passenger */ 156 @OccupantTypeEnum 157 @AddedInOrBefore(majorVersion = 33) 158 public final int occupantType; 159 /** 160 * Represents seat assigned for the occupant. In some system, this can have value of 161 * {@link VehicleAreaSeat#SEAT_UNKNOWN}. 162 */ 163 @VehicleAreaSeat.Enum 164 @AddedInOrBefore(majorVersion = 33) 165 public final int seat; 166 167 /** @hide */ OccupantZoneInfo(int zoneId, @OccupantTypeEnum int occupantType, @VehicleAreaSeat.Enum int seat)168 public OccupantZoneInfo(int zoneId, @OccupantTypeEnum int occupantType, 169 @VehicleAreaSeat.Enum int seat) { 170 this.zoneId = zoneId; 171 this.occupantType = occupantType; 172 this.seat = seat; 173 } 174 175 /** @hide */ OccupantZoneInfo(Parcel in)176 public OccupantZoneInfo(Parcel in) { 177 zoneId = in.readInt(); 178 occupantType = in.readInt(); 179 seat = in.readInt(); 180 } 181 182 @Override 183 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) 184 @AddedInOrBefore(majorVersion = 33) describeContents()185 public int describeContents() { 186 return 0; 187 } 188 189 @Override 190 @AddedInOrBefore(majorVersion = 33) writeToParcel(Parcel dest, int flags)191 public void writeToParcel(Parcel dest, int flags) { 192 dest.writeInt(zoneId); 193 dest.writeInt(occupantType); 194 dest.writeInt(seat); 195 } 196 197 @Override 198 @AddedInOrBefore(majorVersion = 33) equals(Object other)199 public boolean equals(Object other) { 200 if (this == other) { 201 return true; 202 } 203 if (!(other instanceof OccupantZoneInfo)) { 204 return false; 205 } 206 OccupantZoneInfo that = (OccupantZoneInfo) other; 207 return zoneId == that.zoneId && occupantType == that.occupantType 208 && seat == that.seat; 209 } 210 211 @Override 212 @AddedInOrBefore(majorVersion = 33) hashCode()213 public int hashCode() { 214 int hash = 23; 215 hash = hash * 17 + zoneId; 216 hash = hash * 17 + occupantType; 217 hash = hash * 17 + seat; 218 return hash; 219 } 220 221 @AddedInOrBefore(majorVersion = 33) 222 public static final Parcelable.Creator<OccupantZoneInfo> CREATOR = 223 new Parcelable.Creator<OccupantZoneInfo>() { 224 public OccupantZoneInfo createFromParcel(Parcel in) { 225 return new OccupantZoneInfo(in); 226 } 227 228 public OccupantZoneInfo[] newArray(int size) { 229 return new OccupantZoneInfo[size]; 230 } 231 }; 232 233 @Override 234 @AddedInOrBefore(majorVersion = 33) toString()235 public String toString() { 236 StringBuilder b = new StringBuilder(64); 237 b.append("OccupantZoneInfo{zoneId="); 238 b.append(zoneId); 239 b.append(" type="); 240 b.append(occupantType); 241 b.append(" seat="); 242 b.append(Integer.toHexString(seat)); 243 b.append("}"); 244 return b.toString(); 245 } 246 } 247 248 /** Zone config change caused by display changes. A display could have been added / removed. 249 * Besides change in display itself. this can lead into removal / addition of passenger zones. 250 */ 251 @AddedInOrBefore(majorVersion = 33) 252 public static final int ZONE_CONFIG_CHANGE_FLAG_DISPLAY = 0x1; 253 254 /** Zone config change caused by user change. Assigned user for passenger zones have changed. */ 255 @AddedInOrBefore(majorVersion = 33) 256 public static final int ZONE_CONFIG_CHANGE_FLAG_USER = 0x2; 257 258 /** Zone config change caused by audio zone change. 259 * Assigned audio zone for passenger zones have changed. 260 **/ 261 @AddedInOrBefore(majorVersion = 33) 262 public static final int ZONE_CONFIG_CHANGE_FLAG_AUDIO = 0x4; 263 264 /** @hide */ 265 @IntDef(flag = true, prefix = { "ZONE_CONFIG_CHANGE_FLAG_" }, value = { 266 ZONE_CONFIG_CHANGE_FLAG_DISPLAY, 267 ZONE_CONFIG_CHANGE_FLAG_USER, 268 ZONE_CONFIG_CHANGE_FLAG_AUDIO, 269 }) 270 @Retention(RetentionPolicy.SOURCE) 271 @interface ZoneConfigChangeFlags {} 272 273 /** 274 * Listener to monitor any Occupant Zone configuration change. The configuration change can 275 * involve some displays removed or new displays added. Also it can happen when assigned user 276 * for any zone changes. 277 */ 278 public interface OccupantZoneConfigChangeListener { 279 280 /** 281 * Configuration for occupant zones has changed. Apps should re-check all 282 * occupant zone configs. This can be caused by events like user switching and 283 * display addition / removal. 284 * 285 * @param changeFlags Reason for the zone change. 286 */ 287 @AddedInOrBefore(majorVersion = 33) onOccupantZoneConfigChanged(@oneConfigChangeFlags int changeFlags)288 void onOccupantZoneConfigChanged(@ZoneConfigChangeFlags int changeFlags); 289 } 290 291 private final DisplayManager mDisplayManager; 292 private final EventHandler mEventHandler; 293 294 private final ICarOccupantZone mService; 295 296 private final ICarOccupantZoneCallbackImpl mBinderCallback; 297 298 private final CopyOnWriteArrayList<OccupantZoneConfigChangeListener> mListeners = 299 new CopyOnWriteArrayList<>(); 300 301 /** @hide */ 302 @VisibleForTesting CarOccupantZoneManager(Car car, IBinder service)303 public CarOccupantZoneManager(Car car, IBinder service) { 304 super(car); 305 mService = ICarOccupantZone.Stub.asInterface(service); 306 mBinderCallback = new ICarOccupantZoneCallbackImpl(this); 307 mDisplayManager = getContext().getSystemService(DisplayManager.class); 308 mEventHandler = new EventHandler(getEventHandler().getLooper()); 309 } 310 311 /** 312 * Returns all available occupants in the system. If no occupant zone is defined in the system 313 * or none is available at the moment, it will return empty list. 314 */ 315 @NonNull 316 @AddedInOrBefore(majorVersion = 33) getAllOccupantZones()317 public List<OccupantZoneInfo> getAllOccupantZones() { 318 try { 319 return mService.getAllOccupantZones(); 320 } catch (RemoteException e) { 321 return handleRemoteExceptionFromCarService(e, Collections.emptyList()); 322 } 323 } 324 325 /** 326 * Returns all displays assigned for the given occupant zone. If no display is available for 327 * the passenger, it will return empty list. 328 */ 329 @NonNull 330 @AddedInOrBefore(majorVersion = 33) getAllDisplaysForOccupant(@onNull OccupantZoneInfo occupantZone)331 public List<Display> getAllDisplaysForOccupant(@NonNull OccupantZoneInfo occupantZone) { 332 assertNonNullOccupant(occupantZone); 333 try { 334 int[] displayIds = mService.getAllDisplaysForOccupantZone(occupantZone.zoneId); 335 ArrayList<Display> displays = new ArrayList<>(displayIds.length); 336 for (int i = 0; i < displayIds.length; i++) { 337 // quick confidence check while getDisplay can still handle invalid display 338 if (displayIds[i] == Display.INVALID_DISPLAY) { 339 continue; 340 } 341 Display display = mDisplayManager.getDisplay(displayIds[i]); 342 if (display != null) { // necessary as display list could have changed. 343 displays.add(display); 344 } 345 } 346 return displays; 347 } catch (RemoteException e) { 348 return handleRemoteExceptionFromCarService(e, Collections.emptyList()); 349 } 350 } 351 352 /** 353 * Gets the display for the occupant for the specified display type, or returns {@code null} 354 * if no display matches the requirements. 355 * 356 * @param displayType This should be a valid display type and passing 357 * {@link #DISPLAY_TYPE_UNKNOWN} will always lead into {@link null} return. 358 */ 359 @Nullable 360 @AddedInOrBefore(majorVersion = 33) getDisplayForOccupant(@onNull OccupantZoneInfo occupantZone, @DisplayTypeEnum int displayType)361 public Display getDisplayForOccupant(@NonNull OccupantZoneInfo occupantZone, 362 @DisplayTypeEnum int displayType) { 363 assertNonNullOccupant(occupantZone); 364 try { 365 int displayId = mService.getDisplayForOccupant(occupantZone.zoneId, displayType); 366 // quick confidence check while getDisplay can still handle invalid display 367 if (displayId == Display.INVALID_DISPLAY) { 368 return null; 369 } 370 return mDisplayManager.getDisplay(displayId); 371 } catch (RemoteException e) { 372 return handleRemoteExceptionFromCarService(e, null); 373 } 374 } 375 376 /** 377 * Returns the display id for the driver. 378 * 379 * <p>This method just returns the display id for the requested type. The returned display id 380 * may correspond to a private display and the client may not have access to it. 381 * 382 * @param displayType the display type 383 * @return the driver's display id or {@link Display#INVALID_DISPLAY} when no such display 384 * exists 385 * 386 * @hide 387 */ 388 @SystemApi 389 @RequiresPermission(Car.ACCESS_PRIVATE_DISPLAY_ID) 390 @AddedInOrBefore(majorVersion = 33) getDisplayIdForDriver(@isplayTypeEnum int displayType)391 public int getDisplayIdForDriver(@DisplayTypeEnum int displayType) { 392 try { 393 return mService.getDisplayIdForDriver(displayType); 394 } catch (RemoteException e) { 395 return handleRemoteExceptionFromCarService(e, Display.INVALID_DISPLAY); 396 } 397 } 398 399 /** 400 * Gets the audio zone id for the occupant, or returns 401 * {@code CarAudioManager.INVALID_AUDIO_ZONE} if no audio zone matches the requirements. 402 * throws InvalidArgumentException if occupantZone does not exist. 403 * 404 * @hide 405 */ 406 @SystemApi 407 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 408 @AddedInOrBefore(majorVersion = 33) getAudioZoneIdForOccupant(@onNull OccupantZoneInfo occupantZone)409 public int getAudioZoneIdForOccupant(@NonNull OccupantZoneInfo occupantZone) { 410 assertNonNullOccupant(occupantZone); 411 try { 412 return mService.getAudioZoneIdForOccupant(occupantZone.zoneId); 413 } catch (RemoteException e) { 414 return handleRemoteExceptionFromCarService(e, null); 415 } 416 } 417 418 /** 419 * Gets occupant for the audio zone id, or returns {@code null} 420 * if no audio zone matches the requirements. 421 * 422 * @hide 423 */ 424 @Nullable 425 @SystemApi 426 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 427 @AddedInOrBefore(majorVersion = 33) getOccupantForAudioZoneId(int audioZoneId)428 public OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) { 429 try { 430 return mService.getOccupantForAudioZoneId(audioZoneId); 431 } catch (RemoteException e) { 432 return handleRemoteExceptionFromCarService(e, null); 433 } 434 } 435 436 /** 437 * Returns assigned display type for the display. It will return {@link #DISPLAY_TYPE_UNKNOWN} 438 * if type is not specified or if display is no longer available. 439 */ 440 @DisplayTypeEnum 441 @AddedInOrBefore(majorVersion = 33) getDisplayType(@onNull Display display)442 public int getDisplayType(@NonNull Display display) { 443 assertNonNullDisplay(display); 444 try { 445 return mService.getDisplayType(display.getDisplayId()); 446 } catch (RemoteException e) { 447 return handleRemoteExceptionFromCarService(e, DISPLAY_TYPE_UNKNOWN); 448 } 449 } 450 451 /** 452 * Returns android user id assigned for the given zone. It will return 453 * {@link UserHandle#USER_NULL} if user is not assigned or if zone is not available. 454 */ 455 @UserIdInt 456 @AddedInOrBefore(majorVersion = 33) getUserForOccupant(@onNull OccupantZoneInfo occupantZone)457 public int getUserForOccupant(@NonNull OccupantZoneInfo occupantZone) { 458 assertNonNullOccupant(occupantZone); 459 try { 460 return mService.getUserForOccupant(occupantZone.zoneId); 461 } catch (RemoteException e) { 462 return handleRemoteExceptionFromCarService(e, UserManagerHelper.USER_NULL); 463 } 464 } 465 466 /** 467 * Assigns the given profile {@code userId} to the {@code occupantZone}. Returns true when the 468 * request succeeds. 469 * 470 * <p>Note that only non-driver zone can be assigned with this call. Calling this for driver 471 * zone will lead into {@code IllegalArgumentException}. 472 * 473 * @param occupantZone Zone to assign user. 474 * @param userId profile user id to assign. Passing {@link UserHandle#USER_NULL} leads into 475 * removing the current user assignment. 476 * @return true if the request succeeds or if the user is already assigned to the zone. 477 * 478 * @hide 479 */ 480 @RequiresPermission(android.Manifest.permission.MANAGE_USERS) 481 @AddedInOrBefore(majorVersion = 33) assignProfileUserToOccupantZone(@onNull OccupantZoneInfo occupantZone, @UserIdInt int userId)482 public boolean assignProfileUserToOccupantZone(@NonNull OccupantZoneInfo occupantZone, 483 @UserIdInt int userId) { 484 assertNonNullOccupant(occupantZone); 485 try { 486 return mService.assignProfileUserToOccupantZone(occupantZone.zoneId, userId); 487 } catch (RemoteException e) { 488 return handleRemoteExceptionFromCarService(e, false); 489 } 490 } 491 assertNonNullOccupant(OccupantZoneInfo occupantZone)492 private void assertNonNullOccupant(OccupantZoneInfo occupantZone) { 493 if (occupantZone == null) { 494 throw new IllegalArgumentException("null OccupantZoneInfo"); 495 } 496 } 497 assertNonNullDisplay(Display display)498 private void assertNonNullDisplay(Display display) { 499 if (display == null) { 500 throw new IllegalArgumentException("null Display"); 501 } 502 } 503 504 /** 505 * Registers the listener for occupant zone config change. Registering multiple listeners are 506 * allowed. 507 */ 508 @AddedInOrBefore(majorVersion = 33) registerOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)509 public void registerOccupantZoneConfigChangeListener( 510 @NonNull OccupantZoneConfigChangeListener listener) { 511 if (mListeners.addIfAbsent(listener)) { 512 if (mListeners.size() == 1) { 513 try { 514 mService.registerCallback(mBinderCallback); 515 } catch (RemoteException e) { 516 handleRemoteExceptionFromCarService(e); 517 } 518 } 519 } 520 } 521 522 /** 523 * Unregisters the listener. Listeners not registered before will be ignored. 524 */ 525 @AddedInOrBefore(majorVersion = 33) unregisterOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)526 public void unregisterOccupantZoneConfigChangeListener( 527 @NonNull OccupantZoneConfigChangeListener listener) { 528 if (mListeners.remove(listener)) { 529 if (mListeners.size() == 0) { 530 try { 531 mService.unregisterCallback(mBinderCallback); 532 } catch (RemoteException ignored) { 533 // ignore for unregistering 534 } 535 } 536 } 537 } 538 handleOnOccupantZoneConfigChanged(int flags)539 private void handleOnOccupantZoneConfigChanged(int flags) { 540 for (OccupantZoneConfigChangeListener listener : mListeners) { 541 listener.onOccupantZoneConfigChanged(flags); 542 } 543 } 544 545 private final class EventHandler extends Handler { 546 private static final int MSG_ZONE_CHANGE = 1; 547 EventHandler(Looper looper)548 private EventHandler(Looper looper) { 549 super(looper); 550 } 551 552 @Override handleMessage(Message msg)553 public void handleMessage(Message msg) { 554 switch (msg.what) { 555 case MSG_ZONE_CHANGE: 556 handleOnOccupantZoneConfigChanged(msg.arg1); 557 break; 558 default: 559 Log.e(TAG, "Unknown msg not handdled:" + msg.what); 560 break; 561 } 562 } 563 dispatchOnOccupantZoneConfigChanged(int flags)564 private void dispatchOnOccupantZoneConfigChanged(int flags) { 565 sendMessage(obtainMessage(MSG_ZONE_CHANGE, flags, 0)); 566 } 567 } 568 569 private static class ICarOccupantZoneCallbackImpl extends ICarOccupantZoneCallback.Stub { 570 private final WeakReference<CarOccupantZoneManager> mManager; 571 ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager)572 private ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager) { 573 mManager = new WeakReference<>(manager); 574 } 575 576 @Override onOccupantZoneConfigChanged(int flags)577 public void onOccupantZoneConfigChanged(int flags) { 578 CarOccupantZoneManager manager = mManager.get(); 579 if (manager != null) { 580 manager.mEventHandler.dispatchOnOccupantZoneConfigChanged(flags); 581 } 582 } 583 } 584 585 /** @hide */ 586 @Override 587 @AddedInOrBefore(majorVersion = 33) onCarDisconnected()588 public void onCarDisconnected() { 589 // nothing to do 590 } 591 } 592